我有一个字符串,我想用它作为文件名,所以我想用Python删除文件名中不允许的所有字符。

我宁愿严格一点,所以假设我想只保留字母、数字和一小组其他字符,如“_-.()”。”。最优雅的解决方案是什么?

文件名需要在多个操作系统(Windows, Linux和Mac OS)上有效——它是我库中的一个MP3文件,以歌曲标题为文件名,并在3台机器之间共享和备份。


当前回答

这是Windows特定路径的另一个答案,使用简单的替换,没有时髦的模块:

import re

def check_for_illegal_char(input_str):
    # remove illegal characters for Windows file names/paths 
    # (illegal filenames are a superset (41) of the illegal path names (36))
    # this is according to windows blacklist obtained with Powershell
    # from: https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names/44750843#44750843
    #
    # PS> $enc = [system.Text.Encoding]::UTF8
    # PS> $FileNameInvalidChars = [System.IO.Path]::GetInvalidFileNameChars()
    # PS> $FileNameInvalidChars | foreach { $enc.GetBytes($_) } | Out-File -FilePath InvalidFileCharCodes.txt

    illegal = '\u0022\u003c\u003e\u007c\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008' + \
              '\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015' + \
              '\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f\u003a\u002a\u003f\u005c\u002f' 

    output_str, _ = re.subn('['+illegal+']','_', input_str)
    output_str = output_str.replace('\\','_')   # backslash cannot be handled by regex
    output_str = output_str.replace('..','_')   # double dots are illegal too, or at least a bad idea 
    output_str = output_str[:-1] if output_str[-1] == '.' else output_str # can't have end of line '.'

    if output_str != input_str:
        print(f"The name '{input_str}' had invalid characters, "
              f"name was modified to '{output_str}'")

    return output_str

当测试check_for_illegal_char('fas\u0003\u0004good\\..asd.'),我得到:

The name 'fas♥♦good\..asd.' had invalid characters, name was modified to 'fas__good__asd'

其他回答

不完全是OP要求的,但这是我使用的,因为我需要唯一的和可逆的转换:

# p3 code
def safePath (url):
    return ''.join(map(lambda ch: chr(ch) if ch in safePath.chars else '%%%02x' % ch, url.encode('utf-8')))
safePath.chars = set(map(lambda x: ord(x), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-_ .'))

结果“有些”可读,至少从系统管理员的角度来看是这样。

仍然没有找到一个好的库来生成有效的文件名。注意,在德语、挪威语或法语等语言中,文件名中的特殊字符非常常见,完全可以接受。所以我最终有了自己的图书馆:

# util/files.py

CHAR_MAX_LEN = 31
CHAR_REPLACE = '_'

ILLEGAL_CHARS = [
    '#',  # pound
    '%',  # percent
    '&',  # ampersand
    '{',  # left curly bracket
    '}',  # right curly bracket
    '\\',  # back slash
    '<',  # left angle bracket
    '>',  # right angle bracket
    '*',  # asterisk
    '?',  # question mark
    '/',  # forward slash
    ' ',  # blank spaces
    '$',  # dollar sign
    '!',  # exclamation point
    "'",  # single quotes
    '"',  # double quotes
    ':',  # colon
    '@',  # at sign
    '+',  # plus sign
    '`',  # backtick
    '|',  # pipe
    '=',  # equal sign
]


def generate_filename(
        name, char_replace=CHAR_REPLACE, length=CHAR_MAX_LEN, 
        illegal=ILLEGAL_CHARS, replace_dot=False):
    ''' return clean filename '''
    # init
    _elem = name.split('.')
    extension = _elem[-1].strip()
    _length = length - len(extension) - 1
    label = '.'.join(_elem[:-1]).strip()[:_length]
    filename = ''
    
    # replace '.' ?
    if replace_dot:
        label = label.replace('.', char_replace)
    
    # clean
    for char in label + '.' + extension:
        if char in illegal:
            char = char_replace
        filename += char      
    
    return filename

generate_虚构(“nucgae zutaaer .0.1 docx”,replace_dot=False)

nucgae_zutaäer..0.1.docx

generate_虚构(“nucgae zutaaer .0.1 docx”,replace_dot=True)

nucgae_zutaäer__0_1.docx

当遇到同样的问题时,我使用python-slugify。

Shoham也建议使用这种方法,但正如therealmarv指出的那样,默认情况下python-slugify也会转换圆点。

可以通过在regex_pattern参数中包含点来否决这种行为。

> filename = "This is a väryì' Strange File-Nömé.jpeg"
> pattern = re.compile(r'[^-a-zA-Z0-9.]+')
> slugify(filename,regex_pattern=pattern) 
'this-is-a-varyi-strange-file-nome.jpeg'

方法复制的正则表达式模式

ALLOWED_CHARS_PATTERN_WITH_UPPERCASE

python-slugify包的slugify.py文件中的全局变量,并扩展为“。”

请记住,像.()这样的特殊字符必须用\转义。

如果您想保留大写字母,请使用小写=False参数。

> filename = "This is a väryì' Strange File-Nömé.jpeg"
> pattern = re.compile(r'[^-a-zA-Z0-9.]+')
> slugify(filename,regex_pattern=pattern, lowercase=False) 
'This-is-a-varyi-Strange-File-Nome.jpeg'

这是使用Python 3.8.4和Python -slugify 4.0.1实现的

这是Windows特定路径的另一个答案,使用简单的替换,没有时髦的模块:

import re

def check_for_illegal_char(input_str):
    # remove illegal characters for Windows file names/paths 
    # (illegal filenames are a superset (41) of the illegal path names (36))
    # this is according to windows blacklist obtained with Powershell
    # from: https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names/44750843#44750843
    #
    # PS> $enc = [system.Text.Encoding]::UTF8
    # PS> $FileNameInvalidChars = [System.IO.Path]::GetInvalidFileNameChars()
    # PS> $FileNameInvalidChars | foreach { $enc.GetBytes($_) } | Out-File -FilePath InvalidFileCharCodes.txt

    illegal = '\u0022\u003c\u003e\u007c\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008' + \
              '\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015' + \
              '\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f\u003a\u002a\u003f\u005c\u002f' 

    output_str, _ = re.subn('['+illegal+']','_', input_str)
    output_str = output_str.replace('\\','_')   # backslash cannot be handled by regex
    output_str = output_str.replace('..','_')   # double dots are illegal too, or at least a bad idea 
    output_str = output_str[:-1] if output_str[-1] == '.' else output_str # can't have end of line '.'

    if output_str != input_str:
        print(f"The name '{input_str}' had invalid characters, "
              f"name was modified to '{output_str}'")

    return output_str

当测试check_for_illegal_char('fas\u0003\u0004good\\..asd.'),我得到:

The name 'fas♥♦good\..asd.' had invalid characters, name was modified to 'fas__good__asd'

This whitelist approach (ie, allowing only the chars present in valid_chars) will work if there aren't limits on the formatting of the files or combination of valid chars that are illegal (like ".."), for example, what you say would allow a filename named " . txt" which I think is not valid on Windows. As this is the most simple approach I'd try to remove whitespace from the valid_chars and prepend a known valid string in case of error, any other approach will have to know about what is allowed where to cope with Windows file naming limitations and thus be a lot more complex.

>>> import string
>>> valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
>>> valid_chars
'-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
>>> filename = "This Is a (valid) - filename%$&$ .txt"
>>> ''.join(c for c in filename if c in valid_chars)
'This Is a (valid) - filename .txt'