Cách khắc phục 'OSError: [Errno 22]' – Tại sao Windows từ chối tên tệp của bạn

beginner🪟 Windows2026-07-13| Windows 10/11, Python 3.x, Hệ thống tệp NTFS

Error Message

OSError: [Errno 22] Invalid argument: 'output:2024.log'
#python#windows#ntfs#oserror#meo-lap-trinh

Vấn đề: Khi mã nguồn Linux chạy trên đường dẫn Windows

Gần đây tôi đã chuyển một script Python thu thập dữ liệu (data-scraping) từ máy chủ Linux sang máy trạm Windows và nó bị lỗi ngay lập tức. Script này dự kiến lưu log bằng tên tệp chứa mốc thời gian, ví dụ 2024-05-20_14:30:05.log. Trên Linux, điều này là bình thường. Nhưng trên Windows, script gặp lỗi với thông báo:

OSError: [Errno 22] Invalid argument: 'output:2024.log'

Lỗi này về cơ bản là Windows đang báo rằng nó không nhận dạng được định dạng đường dẫn. Thông thường, điều này xảy ra vì tên tệp chứa các ký tự mà hệ thống tệp NTFS dành riêng cho logic nội bộ của nó.

Các ký tự bị cấm

Windows khắt khe hơn Linux rất nhiều. Trong khi Linux chỉ thực sự không thích dấu xẹt xuôi (forward slash) và ký tự null, Windows cấm chín ký tự cụ thể trong tên tệp:

  • < (dấu nhỏ hơn)
  • > (dấu lớn hơn)
  • : (dấu hai chấm)
  • " (dấu nháy kép)
  • / (dấu xẹt xuôi)
  • \ (dấu xẹt ngược)
  • | (dấu gạch đứng)
  • ? (dấu hỏi)
  • * (dấu sao)

Trong trường hợp của tôi, dấu hai chấm (:) là nguyên nhân gây lỗi. Windows sử dụng dấu hai chấm để chỉ định tên ổ đĩa như C: hoặc để xử lý các luồng dữ liệu thay thế (alternate data streams) của NTFS. Nó không cho phép bạn dùng ký tự này như một ký tự bình thường trong tên tệp.

Tái hiện lỗi

Hãy thử tạo một tệp thủ công trong Windows Explorer bằng dấu hai chấm. Windows sẽ chặn bạn ngay lập tức với một cảnh báo tooltip. Tuy nhiên, Python không phát hiện ra điều này cho đến khi nó thực hiện lệnh gọi hệ thống (system call) tới hệ điều hành.

Hãy xem đoạn mã đơn giản sau:

filename = "report:2024.txt"
with open(filename, "w") as f:
    f.write("Dữ liệu")

Hệ điều hành từ chối yêu cầu trước khi Python kịp bắt đầu ghi. Đây là một lệnh dừng cứng ở cấp độ nhân (kernel).

Cách khắc phục: Làm sạch tên tệp

Bạn cần làm sạch (sanitize) các chuỗi ký tự trước khi truyền chúng vào hệ thống tệp. Dưới đây là ba cách thực tế để xử lý vấn đề này.

1. Thay thế bằng Regex (Tốt nhất cho nhu cầu chung)

Thay thế các ký tự không hợp lệ bằng dấu gạch dưới là cách sửa lỗi phổ biến nhất. Nó giúp tên tệp vẫn dễ đọc trong khi vẫn đáp ứng các yêu cầu của hệ điều hành.

import re

def make_windows_safe(filename):
    # Thay thế bất kỳ ký tự nào trong 9 ký tự dành riêng bằng dấu gạch dưới
    return re.sub(r'[<>:"/\\|?*]', '_', filename)

raw_name = "output:2024.log"
safe_name = make_windows_safe(raw_name)

# Bây giờ tệp sẽ được lưu là 'output_2024.log'
with open(safe_name, "w") as f:
    f.write("Nội dung log...")

2. Mã hóa URL (Tốt nhất để bảo toàn dữ liệu)

Đôi khi bạn cần giữ nguyên chuỗi gốc một cách chính xác, có thể vì đó là một URL hoặc một ID duy nhất. Trong những trường hợp này, việc thay thế ký tự bằng dấu gạch dưới sẽ làm mất quá nhiều thông tin. Mã hóa URL là một lựa chọn thay thế tốt hơn.

Tôi thường sử dụng công cụ URL Encoder/Decoder tại toolcraft.app để kiểm tra các chuỗi phức tạp sẽ trông như thế nào trước khi áp dụng logic vào mã nguồn. Điều này giúp phát hiện sớm các vấn đề về mã hóa kép (double-encoding).

import urllib.parse

raw_name = "output:2024.log"
safe_name = urllib.parse.quote(raw_name, safe='')

# Kết quả: 'output%3A2024.log'
with open(safe_name, "w") as f:
    f.write("Dữ liệu đã mã hóa")

3. Cẩn thận với các tên dành riêng

Ngay cả một cái tên "sạch" cũng có thể thất bại. Windows có một danh sách các từ dành riêng từ thời DOS. Những tên tệp như CON, PRN, AUX, NUL, COM1, hoặc LPT1 sẽ gây ra lỗi ngay cả khi không có ký tự đặc biệt. Nếu mã của bạn tạo ra những tên này, hãy thêm một hậu tố như _file vào cuối.

Kiểm tra logic

Luôn xác minh quá trình làm sạch của bạn. Tôi sử dụng một vòng lặp đơn giản để kiểm tra nhiều tên "bất hợp pháp" khác nhau nhằm đảm bảo script không bị treo khi chạy thực tế (production).

import os

# Kết hợp dấu xẹt, dấu gạch đứng và dấu hai chấm
bad_names = ["test:file.txt", "data/2024.csv", "is_it_real?.log", "pipe|name.txt"]

for name in bad_names:
    safe_name = re.sub(r'[<>:"/\\|?*]', '_', name)
    try:
        with open(safe_name, 'w') as f:
            f.write("thành công")
        print(f"Đã tạo: {safe_name}")
        os.remove(safe_name) 
    except OSError as e:
        print(f"Thất bại với {safe_name}: {e}")

Những lưu ý quan trọng

  • Lưu ý giới hạn 255 ký tự: NTFS cho phép tên tệp dài tối đa 255 ký tự. Nếu đường dẫn của bạn vượt quá 260 ký tự (MAX_PATH), bạn sẽ gặp một lỗi hoàn toàn khác.
  • Chuẩn hóa định dạng ngày tháng: Thay vì dùng 14:30:05, hãy dùng 14-30-05. Cách này an toàn hơn cho các script chạy trên nhiều nền tảng.
  • Linux không phải là Windows: Việc script chạy tốt trên máy phát triển hoặc trong container Docker không có nghĩa là nó sẽ chạy được trên laptop Windows của đồng nghiệp. Hãy luôn làm sạch tên tệp.

Related Error Notes