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

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

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


当前回答

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

其他回答

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

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

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

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

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

字符串都是无效字符(留下一个空字符串) 你最终会得到一个具有特殊含义的字符串,例如"."或".." 在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

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

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

import re

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

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

使用字符串作为文件名的原因是什么?如果人类的可读性不是一个因素,我会使用base64模块,它可以产生文件系统安全字符串。它是不可读的,但你不需要处理碰撞,而且它是可逆的。

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

更新:根据Matthew的评论修改。

就像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)