我有一个字符串,我想用它作为文件名,所以我想用Python删除文件名中不允许的所有字符。
我宁愿严格一点,所以假设我想只保留字母、数字和一小组其他字符,如“_-.()”。”。最优雅的解决方案是什么?
文件名需要在多个操作系统(Windows, Linux和Mac OS)上有效——它是我库中的一个MP3文件,以歌曲标题为文件名,并在3台机器之间共享和备份。
我有一个字符串,我想用它作为文件名,所以我想用Python删除文件名中不允许的所有字符。
我宁愿严格一点,所以假设我想只保留字母、数字和一小组其他字符,如“_-.()”。”。最优雅的解决方案是什么?
文件名需要在多个操作系统(Windows, Linux和Mac OS)上有效——它是我库中的一个MP3文件,以歌曲标题为文件名,并在3台机器之间共享和备份。
当前回答
你可以看看Django框架(但要考虑到许可!),看看他们如何从任意文本中创建一个“slug”。段代码是URL和文件名友好的。
Django文本utils定义了一个函数,slugify(),这可能是这种事情的黄金标准。本质上,他们的代码如下。
import unicodedata
import re
def slugify(value, allow_unicode=False):
"""
Taken from https://github.com/django/django/blob/master/django/utils/text.py
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
dashes to single dashes. Remove characters that aren't alphanumerics,
underscores, or hyphens. Convert to lowercase. Also strip leading and
trailing whitespace, dashes, and underscores.
"""
value = str(value)
if allow_unicode:
value = unicodedata.normalize('NFKC', value)
else:
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = re.sub(r'[^\w\s-]', '', value.lower())
return re.sub(r'[-\s]+', '-', value).strip('-_')
旧版本是:
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
"""
import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
value = unicode(re.sub('[-\s]+', '-', value))
# ...
return value
还有更多,但我把它省略了,因为它没有解决怠惰,而是逃避。
其他回答
更复杂的是,仅仅删除无效字符并不能保证获得有效的文件名。由于允许的字符在不同的文件名上不同,保守的方法可能最终会将一个有效的名称变成一个无效的名称。你可能想在以下情况下添加特殊处理:
字符串都是无效字符(留下一个空字符串) 你最终会得到一个具有特殊含义的字符串,例如"."或".." 在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
您可能可以通过在文件名前添加一些字符串来解决这些问题,这些字符串永远不会导致这些情况之一,并剥离无效字符。
我相信这不是一个很好的答案,因为它修改了它循环的字符串,但它似乎工作得很好:
import string
for chr in your_string:
if chr == ' ':
your_string = your_string.replace(' ', '_')
elif chr not in string.ascii_letters or chr not in string.digits:
your_string = your_string.replace(chr, '')
这是我最终使用的解决方案:
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调用将重音字符替换为同等的非重音字符,这比简单地将它们剥离要好。之后,所有不允许的字符都将被删除。
我的解决方案没有预先添加一个已知字符串,以避免可能的不允许文件名,因为我知道给定特定的文件名格式,它们不会出现。这需要一个更普遍的解决方案。
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'
其他注释还没有解决的另一个问题是空字符串,这显然不是一个有效的文件名。你也可能因为剥离太多字符而得到一个空字符串。
对于Windows保留文件名和点的问题,最安全的回答是“如何从任意用户输入规范化有效文件名?”就是“连试都不用试”:如果你能找到任何其他方法来避免它(例如。使用来自数据库的整数主键作为文件名),这样做。
如果你必须,你真的需要允许空格和。’将文件扩展名作为名称的一部分,可以这样尝试:
import re
badchars= re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$')
badnames= re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)')
def makeName(s):
name= badchars.sub('_', s)
if badnames.match(name):
name= '_'+name
return name
即使这样也不能保证正确,特别是在意想不到的操作系统上-例如RISC操作系统讨厌空格并使用'。'作为目录分隔符。