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

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

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


当前回答

就像S.Lott回答的那样,你可以看看Django框架如何将字符串转换为有效的文件名。

最新和更新的版本在utils/text.py中,并定义了"get_valid_filename",如下所示:

def get_valid_filename(s):
    s = str(s).strip().replace(' ', '_')
    return re.sub(r'(?u)[^-\w.]', '', s)

(见https://github.com/django/django/blob/master/django/utils/text.py)

其他回答

>>> import string
>>> safechars = bytearray(('_-.()' + string.digits + string.ascii_letters).encode())
>>> allchars = bytearray(range(0x100))
>>> deletechars = bytearray(set(allchars) - set(safechars))
>>> filename = u'#ab\xa0c.$%.txt'
>>> safe_filename = filename.encode('ascii', 'ignore').translate(None, deletechars).decode()
>>> safe_filename
'abc..txt'

它不处理空字符串,特殊文件名('nul', 'con'等)。

一句话:

valid_file_name = re.sub('[^\w_.)( -]', '', any_string)

你也可以用“_”字符让它更具可读性(例如替换斜杠)

我知道有很多答案,但它们大多依赖于正则表达式或外部模块,所以我想抛出我自己的答案。一个纯python函数,不需要外部模块,不使用正则表达式。我的方法不是清除无效字符,而是只允许有效字符。

def normalizefilename(fn):
    validchars = "-_.() "
    out = ""
    for c in fn:
      if str.isalpha(c) or str.isdigit(c) or (c in validchars):
        out += c
      else:
        out += "_"
    return out    

如果您愿意,您可以在开头向validchars变量添加您自己的有效字符,例如您的国家字母在英语字母表中不存在。这是您可能想要也可能不想要的:一些不运行UTF-8的文件系统在使用非ascii字符时可能仍然存在问题。

此函数用于测试单个文件名的有效性,因此它将路径分隔符替换为_,认为它们是无效字符。如果你想添加它,修改If以包含os路径分隔符是很简单的。

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

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

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

# 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