The Problem: When Linux Code Meets Windows Paths
I recently moved a Python data-scraping script from a Linux server to a Windows workstation, and it broke instantly. The script was supposed to save logs using a timestamped filename, like 2024-05-20_14:30:05.log. On Linux, this is standard. On Windows, the script crashed with a blunt error message:
OSError: [Errno 22] Invalid argument: 'output:2024.log'
This error is essentially Windows saying it doesn't recognize the path format. Usually, this happens because the filename includes characters that the NTFS file system reserves for its own internal logic.
The Forbidden Characters
Windows is much pickier than Linux. While Linux only really hates forward slashes and the null character, Windows forbids nine specific characters in filenames:
<(less than)>(greater than):(colon)"(double quote)/(forward slash)\(backslash)|(vertical bar)?(question mark)*(asterisk)
In my case, the colon (:) caused the crash. Windows uses colons to designate drive letters like C: or to handle NTFS alternate data streams. It won't let you use one as a regular character in a filename.
Replicating the Crash
Try creating a file manually in Windows Explorer using a colon. Windows blocks you immediately with a tooltip warning. Python, however, doesn't catch this until it makes the system call to the OS.
Consider this simple snippet:
filename = "report:2024.txt"
with open(filename, "w") as f:
f.write("Data")
The OS rejects the request before Python can even start writing. It's a hard stop at the kernel level.
The Fix: Cleaning Your Filenames
You need to sanitize your strings before passing them to the file system. Here are three practical ways to handle this.
1. The Regex Swap (Best for General Use)
Swapping invalid characters for an underscore is the most common fix. It keeps the filename readable while satisfying the OS requirements.
import re
def make_windows_safe(filename):
# Replace any of the 9 reserved characters with an underscore
return re.sub(r'[<>:"/\\|?*]', '_', filename)
raw_name = "output:2024.log"
safe_name = make_windows_safe(raw_name)
# This now saves as 'output_2024.log'
with open(safe_name, "w") as f:
f.write("Log entry...")
2. URL Encoding (Best for Data Integrity)
Sometimes you need to preserve the original string exactly, perhaps because it's a URL or a unique ID. In these cases, replacing characters with underscores loses too much information. URL encoding is a better alternative.
I often use the URL Encoder/Decoder at toolcraft.app to check how complex strings will look before I commit the logic to my code. This helps catch double-encoding issues early.
import urllib.parse
raw_name = "output:2024.log"
safe_name = urllib.parse.quote(raw_name, safe='')
# Result: 'output%3A2024.log'
with open(safe_name, "w") as f:
f.write("Encoded data")
3. Watching Out for Reserved Names
Even a "clean" name can fail. Windows has a list of legacy reserved words from the DOS era. Filenames like CON, PRN, AUX, NUL, COM1, or LPT1 will trigger an error even without special characters. If your code generates these, add a suffix like _file to the end.
Testing the Logic
Always verify your sanitization. I use a simple loop to test a variety of "illegal" names to ensure the script doesn't hang in production.
import os
# A mix of slashes, pipes, and colons
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("success")
print(f"Created: {safe_name}")
os.remove(safe_name)
except OSError as e:
print(f"Failed on {safe_name}: {e}")
Key Takeaways
- Mind the 255-character limit: NTFS allows filenames up to 255 characters. If your path exceeds 260 characters (MAX_PATH), you'll hit a different error entirely.
- Standardize your dates: Instead of
14:30:05, use14-30-05. Itβs safer for cross-platform scripts. - Linux is not Windows: Just because it works on your dev machine or a Docker container doesn't mean it will work on a colleague's Windows laptop. Always sanitize.

