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

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

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


当前回答

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

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

其他回答

你可以看看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

还有更多,但我把它省略了,因为它没有解决怠惰,而是逃避。

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

import re

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

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

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

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

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-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实现的