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

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

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


当前回答

当遇到同样的问题时,我使用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实现的

其他回答

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

# 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

为什么不直接用try/except来包装“osopen”,让底层操作系统来判断文件是否有效?

这看起来工作量少得多,而且无论您使用哪种操作系统都是有效的。

更复杂的是,仅仅删除无效字符并不能保证获得有效的文件名。由于允许的字符在不同的文件名上不同,保守的方法可能最终会将一个有效的名称变成一个无效的名称。你可能想在以下情况下添加特殊处理:

字符串都是无效字符(留下一个空字符串) 你最终会得到一个具有特殊含义的字符串,例如"."或".." 在windows中,某些设备名称是保留的。例如,你不能创建一个名为“nul”,“null .txt”(或nul.txt)的文件。)保留的名字是: CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9

您可能可以通过在文件名前添加一些字符串来解决这些问题,这些字符串永远不会导致这些情况之一,并剥离无效字符。

为python 3.6修改的答案

import string
import unicodedata

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(chr(c) for c in cleanedFilename if chr(c) in validFilenameChars)

你可以使用re.sub()方法替换任何非“类文件”的东西。但实际上,每个字符都可以是有效的;所以没有预先构建的函数(我相信)来完成它。

import re

str = "File!name?.txt"
f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))

将导致/tmp/filename.txt的文件句柄。