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

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

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


当前回答

我喜欢这里的python-slugify方法,但它也剥离点,这是不希望的。所以我优化了上传一个干净的文件名到s3:

pip install python-slugify

示例代码:

s = 'Very / Unsafe / file\nname hähä \n\r .txt'
clean_basename = slugify(os.path.splitext(s)[0])
clean_extension = slugify(os.path.splitext(s)[1][1:])
if clean_extension:
    clean_filename = '{}.{}'.format(clean_basename, clean_extension)
elif clean_basename:
    clean_filename = clean_basename
else:
    clean_filename = 'none' # only unclean characters

输出:

>>> clean_filename
'very-unsafe-file-name-haha.txt'

这是如此的故障安全,它适用于没有扩展名的文件名,甚至只适用于不安全的字符文件名(这里的结果是none)。

其他回答

您可以将列表推导式与字符串方法一起使用。

>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'

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'

我知道有很多答案,但它们大多依赖于正则表达式或外部模块,所以我想抛出我自己的答案。一个纯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路径分隔符是很简单的。

这是我最终使用的解决方案:

import unicodedata

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)

def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(c for c in cleanedFilename if c in validFilenameChars)

unicodedata。Normalize调用将重音字符替换为同等的非重音字符,这比简单地将它们剥离要好。之后,所有不允许的字符都将被删除。

我的解决方案没有预先添加一个已知字符串,以避免可能的不允许文件名,因为我知道给定特定的文件名格式,它们不会出现。这需要一个更普遍的解决方案。

不过你得小心点。如果你只看拉丁语言,在你的介绍中没有清楚地说出来。如果您仅使用ascii字符对某些单词进行消毒,它们可能会变得毫无意义或具有其他含义。

假设你有“forêt poésie”(森林诗歌),你的消毒可能会给“堡垒-posie”(强大+无意义的东西)

如果你必须处理汉字,那就更糟了。

“下北沢”您的系统可能最终会执行“——”,这注定会在一段时间后失败,而且没有多大帮助。因此,如果您只处理文件,我建议您将它们称为您控制的通用链,或者保持字符原样。对于uri,大致相同。