从任何(不安全)字符串创建(健全/安全)文件名

Alb*_*ert 44 python cocoa filenames pyobjc

我想从一些随机的Unicode字符串(mich可能只包含任何东西)创建一个健全/安全的文件名(即有些可读,没有"奇怪"字符等).

(对我来说无关紧要,函数是Cocoa,ObjC,Python等)


当然,可能会有无数的字符可能很奇怪.因此,拥有黑名单并在一段时间内向该列表添加越来越多的内容并不是真正的解决方案.

我可以有一个白名单.但是,我真的不知道如何定义它.[a-zA-Z0-9 .]是一个开始,但我也想接受可以正常方式显示的unicode字符.

Rem*_*emi 67

蟒蛇:

"".join([c for c in filename if c.isalpha() or c.isdigit() or c==' ']).rstrip()
Run Code Online (Sandbox Code Playgroud)

这接受Unicode字符,但删除换行符等.

例:

filename = u"ad\nbla'{-+\)(ç?"
Run Code Online (Sandbox Code Playgroud)

得到: adblaç

编辑 str.isalnum()在一步上做字母数字. - 来自下面的queueoverflow的评论.danodonovan暗示要保持一个点.

    keepcharacters = (' ','.','_')
    "".join(c for c in filename if c.isalnum() or c in keepcharacters).rstrip()
Run Code Online (Sandbox Code Playgroud)

  • 这在 Windows 上是不安全的。首先,您需要防范像 CON 和 NUL 这样的旧设备文件名。其次,区分大小写呢?您可能会意外覆盖另一个文件。第三,Windows 上的 Python 无法正确处理末尾带有空格的文件名。至少有三种方法可以将它从我的头顶上打破。 (2认同)
  • 要*不*删除句点(句号)`.`尝试`"".join(如果c.isalnum()或c在['','.']中,则c表示文件名中的c.)rstrip() (2认同)

Mit*_*ers 12

我不建议使用任何其他答案。它们臃肿,使用糟糕的技术,并替换了大量的合法字符(有些甚至删除了所有 Unicode 字符,这很疯狂,因为它们在文件名中是合法的)。其中一些甚至只是为了这个微小而简单的工作而导入巨大的库......这太疯狂了。

这是一个正则表达式单行代码,它可以有效地替换每个非法文件系统字符,而不是其他字符。没有库,没有膨胀,只有一个简单命令中的完全合法的文件名。

参考: https: //en.wikipedia.org/wiki/Filename#Reserved_characters_and_words

正则表达式:

clean = re.sub(r"[/\\?%*:|\"<>\x7F\x00-\x1F]", "-", dirty)
Run Code Online (Sandbox Code Playgroud)

用法:

import re

# Here's a dirty, illegal filename full of control-characters and illegal chars.
dirty = "".join(["\\[/\\?%*:|\"<>0x7F0x00-0x1F]", chr(0x1F) * 15])

# Clean it in one fell swoop.
clean = re.sub(r"[/\\?%*:|\"<>\x7F\x00-\x1F]", "-", dirty)

# Result: "-[----------0x7F0x00-0x1F]---------------"
print(clean)
Run Code Online (Sandbox Code Playgroud)

这是一个极端的例子,几乎每个字符都是非法的,因为我们使用正则表达式删除的相同字符列表构建了脏字符串,我们甚至在末尾填充了一堆“0x1F(ascii 31)”只是为了显示它还删除了非法控制字符。

就是这个。这个正则表达式是您需要的唯一答案。它处理现代文件系统(Mac、Windows 和 Linux)上的所有非法字符。删除超出此范围的任何内容都属于“美化”类别,与制作合法的磁盘文件名无关


Windows 用户的更多工作:

运行此命令后,您可以选择根据 Windows 上的“特殊设备名称”列表(不区分大小写的单词列表,例如“CON”、“AUX”、“COM0”等)检查结果

可以在https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations中找到NTFSFAT文件系统的“保留字”和“注释”列中的非法字。

仅当您计划将文件存储在 NTFS 或 FAT 类型的磁盘上时,才需要过滤保留字。因为Windows保留了某些“神奇文件名”供内部使用。它保留它们时不区分大小写,并且不关心扩展名,这意味着例如在 Windows 上aux.c非法文件名(非常愚蠢)。

所有 Mac/Linux 文件系统都没有这样的愚蠢限制,因此如果您使用的是良好的文件系统,则无需执行任何其他操作。哎呀,事实上,我们在正则表达式中过滤掉的大多数“非法字符”都是 Windows 特定的限制。Mac/Linux 文件系统可以存储其中的大部分。但我们无论如何都会过滤它们,因为它使文件名可移植到 Windows 计算机。


Ngu*_*aga 11

我的要求是保守的(生成的文件名需要在多个操作系统上有效,包括一些古老的移动操作系统).我结束了:

    "".join([c for c in text if re.match(r'\w', c)])
Run Code Online (Sandbox Code Playgroud)

该白色列出了字母数字字符(az,AZ,0-9)和下划线.如果要匹配很多字符串,则可以编译和缓存正则表达式以提高效率.就我而言,它不会产生任何重大影响.


Fil*_*ina 8

或多或少在这里提到的正则表达式,但反过来(替换任何未列出的):

>>> import re
>>> filename = u"ad\nbla'{-+\)(ç1?"
>>> re.sub(r'[^\w\d-]','_',filename)
u'ad_bla__-_____1_'
Run Code Online (Sandbox Code Playgroud)

  • 只需使用“\W”即可“匹配除字母、数字或下划线之外的任何内容。相当于“[^a-zA-Z0-9_]””。 (3认同)

ugl*_*ote 6

这里有一些合理的答案,但就我而言,我想采用的东西可能是带有空格和标点符号的字符串,而不仅仅是删除它们,我宁愿用下划线代替。即使在大多数操作系统中,空格是允许的文件名字符,它们还是有问题的。另外,在我的情况下,如果原始字符串包含句点,则我不希望该句点传递到文件名中,否则它将生成我可能不希望的“额外扩展名”(我自己添加了扩展名)

def make_safe_filename(s):
    def safe_char(c):
        if c.isalnum():
            return c
        else:
            return "_"
    return "".join(safe_char(c) for c in s).rstrip("_")

print(make_safe_filename( "hello you crazy $#^#& 2579 people!!! : die!!!" ) + ".gif")
Run Code Online (Sandbox Code Playgroud)

印刷品:

hello_you_crazy _______ 2579_people ______ die ___。gif

  • 我认为如果将重复的下划线替换为单个下划线,该功能可能会更好。`re.sub('_{2,}', '_', 'hello_you_crazy_______2579_people__die___.gif')` `` `&gt;&gt; 'hello_you_crazy_2579_people_die_.gif'` (2认同)

Cha*_*imG 6

许多其他答案的问题是它们只处理字符替换;不是其他问题。

这是一个全面的通用解决方案。它为您处理所有类型的问题,包括(但不限于)字符替换。它应该涵盖所有基础。

适用于 Windows、*nix 和几乎所有其他文件系统。

def txt2filename(txt, chr_set='printable'):
    """Converts txt to a valid filename.

    Args:
        txt: The str to convert.
        chr_set:
            'printable':    Any printable character except those disallowed on Windows/*nix.
            'extended':     'printable' + extended ASCII character codes 128-255
            'universal':    For almost *any* file system. '-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    """

    FILLER = '-'
    MAX_LEN = 255  # Maximum length of filename is 255 bytes in Windows and some *nix flavors.

    # Step 1: Remove excluded characters.
    BLACK_LIST = set(chr(127) + r'<>:"/\|?*')                           # 127 is unprintable, the rest are illegal in Windows.
    white_lists = {
        'universal': {'-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'},
        'printable': {chr(x) for x in range(32, 127)} - BLACK_LIST,     # 0-32, 127 are unprintable,
        'extended' : {chr(x) for x in range(32, 256)} - BLACK_LIST,
    }
    white_list = white_lists[chr_set]
    result = ''.join(x
                     if x in white_list else FILLER
                     for x in txt)

    # Step 2: Device names, '.', and '..' are invalid filenames in Windows.
    DEVICE_NAMES = 'CON,PRN,AUX,NUL,COM1,COM2,COM3,COM4,' \
                   'COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,' \
                   'LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,' \
                   'CONIN$,CONOUT$,..,.'.split(',')  # This list is an O(n) operation.
    if result in DEVICE_NAMES:
        result = f'{FILLER}{result}{FILLER}'

    # Step 3: Truncate long files while preserving the file extension.
    if len(result) > MAX_LEN:
        if '.' in txt:
            result, _, ext = result.rpartition('.')
            ext = '.' + ext
        else:
            ext = ''
        result = result[:MAX_LEN - len(ext)] + ext

    # Step 4: Windows does not allow filenames to end with '.' or ' ' or begin with ' '.
    result = re.sub(r'^[. ]', FILLER, result)
    result = re.sub(r' $', FILLER, result)

    return result
Run Code Online (Sandbox Code Playgroud)

它会替换不可打印的字符,即使它们在技术上是有效的文件名,因为它们并不总是易于处理。

不需要外部库。


Dra*_*gon 5

这里没有解决方案,只有您必须考虑的问题:

  • 你的最小最大文件名长度是多少?(例如,DOS 仅支持 8-11 个字符;大多数操作系统不支持 >256 个字符)

  • 在某些情况下禁止哪些文件名?(Windows 仍然不支持将文件另存为CON.TXT- 请参阅https://blogs.msdn.microsoft.com/oldnewthing/20031022-00/?p=42073

  • 请记住...具有特定含义(当前/父目录),因此是不安全的。

  • 是否存在文件名冲突的风险 - 由于删除字符或多次使用相同的文件名?

考虑只对数据进行散列并将其十六进制转储用作文件名?


And*_*s_K 5

如果你不介意导入其他包,那么 werkzeug 有一个清理字符串的方法:

from werkzeug.utils import secure_filename

secure_filename("hello.exe")
'hello.exe'
secure_filename("/../../.ssh")
'ssh'
secure_filename("DROP TABLE")
'DROP_TABLE'

#fork bomb on Linux
secure_filename(": () {: |: &} ;:")
''

#delete all system files on Windows
secure_filename("del*.*")
'del'
Run Code Online (Sandbox Code Playgroud)

https://pypi.org/project/Werkzeug/

  • 该工具是一个完整的WSGI应用程序,相关代码可以在这里找到:https://github.com/pallets/werkzeug/blob/a3b4572a34269efaca4d91fa4cd07dd7f6f94b6d/src/werkzeug/utils.py#L174-L218 *(注意:切勿随机信任代码来自互联网,出于安全考虑,请在使用前验证代码)*!! (2认同)