首页 文章

将字符串转换为有效的文件名?

提问于
浏览
235

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

我说我想只保留字母,数字和一小部分其他字符,如 "_-.() " . 什么是最优雅的解决方案?

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

20 回答

  • 95

    您可以查看Django framework,了解它们如何从任意文本创建"slug" . slug是URL和文件名友好的 .

    他们的 template/defaultfilters.py (在第183行左右)定义了一个函数 slugify ,这可能是这种事情的黄金标准 . 基本上,他们的代码如下 .

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

    还有更多,但是我把它排除在外,因为它没有解决挫折问题,而是逃避了 .

  • 21

    这种白名单方法(即,只允许valid_chars中存在的字符)将起作用,如果没有't limits on the formatting of the files or combination of valid chars that are illegal (like 909201 ), for example, what you say would allow a filename named 909202 which I think is not valid on Windows. As this is the most simple approach I'尝试从valid_chars中删除空格并在出现错误时添加已知的有效字符串,则任何其他方法都必须知道允许的内容在哪里应对Windows file naming limitations,因此要复杂得多 .

    >>> 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'
    
  • 13

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

    import base64
    file_name_string = base64.urlsafe_b64encode(your_string)
    

    Update :根据马修评论改变 .

  • 130

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

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

    只是为了使事情进一步复杂化,您不能保证仅通过删除无效字符就能获得有效的文件名 . 由于允许的字符在不同的文件名上有所不同,因此保守的方法可能最终将有效名称转换为无效名称 . 您可能希望为以下情况添加特殊处理:

    • 字符串是所有无效字符(留下空字符串)

    • 您最终会得到一个具有特殊含义的字符串,例如“ . ”要么 ”..”

    • 在Windows上,保留certain device names . 例如,您无法创建名为"nul","nul.txt"(或实际上为nul.anything)的文件 . 保留名称为:

    CON,PRN,AUX,NUL,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8和LPT9

    您可以解决这些问题,方法是将一些字符串添加到永远不会导致其中一种情况的文件名,并删除无效字符 .

  • 5

    Github上有一个名为_909212的好项目:

    安装:

    pip install python-slugify
    

    然后使用:

    >>> from slugify import slugify
    >>> txt = "This\ is/ a%#$ test ---"
    >>> slugify(txt)
    'this-is-a-test'
    
  • 87

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

    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调用用非重音等效替换重音字符,这比简单地剥离它们要好 . 之后,所有不允许的字符都被删除 .

    我的解决方案没有预先添加已知的字符串以避免可能的不允许的文件名,因为我知道在给定我的特定文件名格式时它们不会发生 . 需要更通用的解决方案 .

  • 6

    请记住,除了Unix系统之外,文件名实际上没有限制

    • 可能不包含\ 0

    • 可能不包含/

    其他一切都是公平的游戏 .

    $ touch "
    > even multiline
    > haha
    > ^[[31m red ^[[0m
    > evil"
    $ ls -la 
    -rw-r--r--       0 Nov 17 23:39 ?even multiline?haha??[31m red ?[0m?evil
    $ ls -lab
    -rw-r--r--       0 Nov 17 23:39 \neven\ multiline\nhaha\n\033[31m\ red\ \033[0m\nevil
    $ perl -e 'for my $i ( glob(q{./*even*}) ){ print $i; } '
    ./
    even multiline
    haha
     red 
    evil
    

    是的,我只是将ANSI颜色代码存储在文件名中并让它们生效 .

    为了娱乐,在目录名称中放入一个BEL字符,并观看CD刻录时的乐趣;)

  • 18

    就像S.Lott回答一样,您可以查看Django Framework,了解它们如何将字符串转换为有效的文件名 .

    最新和更新的版本可以在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

  • 7

    您可以使用re.sub()方法替换不是“filelike”的任何内容 . 但实际上,每个角色都是有效的;所以没有预先构建的功能(我相信),以完成它 .

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

    会导致文件句柄为/tmp/filename.txt .

  • 0
    >>> 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'等) .

  • 5

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

    这似乎更少的工作,无论您使用哪种操作系统,它都是有效的 .

  • 2

    在一行中:

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

    你也可以加'_'字符使其更具可读性(例如,如果更换斜杠)

  • 6

    其他评论尚未解决的另一个问题是空字符串,这显然不是有效的文件名 . 您也可以通过剥离太多字符来结束空字符串 .

    什么与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 OS讨厌空格并使用' . '作为目录分隔符 .

  • 0

    虽然你必须要小心 . 如果您只关注拉丁语言,那么在您的介绍中并没有明确说明 . 如果您仅使用ascii字符对其进行消毒,则某些单词可能会变得毫无意义或其他含义 .

    想象你有“forêtpoésie”(森林诗歌),你的消毒可能会给“堡垒”(强烈的东西毫无意义)

    如果你不得不处理汉字,那就更糟了 .

    “下北沢”你的系统可能最终会做“---”,注定会在一段时间后失败并且不是很有帮助 . 因此,如果您只处理文件,我会鼓励将它们称为您控制的通用链或保持字符不变 . 对于URI,大致相同 .

  • 85

    大多数这些解决方案都不起作用 .

    '/ hello / world' - >'helloworld'

    '/ helloworld'/ - >'helloworld'

    这通常不是你想要的,比如你要保存每个链接的html,你要覆盖不同网页的html .

    我腌制了一个如:

    {'helloworld': 
        (
        {'/hello/world': 'helloworld', '/helloworld/': 'helloworld1'},
        2)
        }
    

    2表示应附加到下一个文件名的数字 .

    我每次从字典中查找文件名 . 如果它不存在,我创建一个新的,如果需要附加最大数量 .

  • 2

    我喜欢这里的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 这里) .

  • 7

    不完全是OP所要求的,但这是我使用的,因为我需要独特和可逆的转换:

    # p3 code
    def safePath (url):
        return ''.join(map(lambda ch: chr(ch) if ch in safePath.chars else '%%%02x' % ch, url.encode('utf-8')))
    safePath.chars = set(map(lambda x: ord(x), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-_ .'))
    

    结果“有些”可读,至少从系统管理员的角度来看 .

  • 1

    我确定这不是一个很好的答案,因为它修改了它循环的字符串,但似乎工作正常:

    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, '')
    
  • 33

    UPDATE

    在这个6岁的答案中,所有链接都无法修复 .

    此外,我也不会这样做,只是 base64 编码或丢弃不安全的字符 . Python 3示例:

    import re
    t = re.compile("[a-zA-Z0-9.,_-]")
    unsafe = "abc∂éåß®∆˚˙©¬ñ√ƒµ©∆∫ø"
    safe = [ch for ch in unsafe if t.match(ch)]
    # => 'abc'
    

    使用 base64 ,您可以进行编码和解码,这样您就可以再次检索原始文件名 .

    但根据用例,您可能最好生成随机文件名并将元数据存储在单独的文件或数据库中 .

    from random import choice
    from string import ascii_lowercase, ascii_uppercase, digits
    allowed_chr = ascii_lowercase + ascii_uppercase + digits
    
    safe = ''.join([choice(allowed_chr) for _ in range(16)])
    # => 'CYQ4JDKE9JfcRzAZ'
    

    ORIGINAL LINKROTTEN ANSWER

    bobcat 项目包含一个执行此操作的python模块 .

    它并不完全健壮,请看postreply .

    因此,如上所述:如果可读性无关紧要, base64 编码可能是一个更好的主意 .

相关问题