把一个string转换成有效的文件名?
我有一个string,我想用作一个文件名,所以我想要删除所有不允许在文件名中使用Python的字符。
我宁愿要比其他方式严格,所以我想说只保留字母,数字和一小部分其他字符,如"_-.() "
。 什么是最优雅的解决scheme?
文件名需要在多个操作系统(Windows,Linux和Mac OS)上有效 – 这是我的库中的一个MP3文件,歌曲标题作为文件名,在3台机器之间共享和备份。
你可以看看Django框架是如何从任意文本中创build一个“slug”的。 一个slu is是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))
还有更多的东西,但我把它排除在外,因为它没有解决slu,,而是逃避。
这种白名单方法(即只允许valid_chars中存在的字符),如果没有文件格式的限制或非法的有效字符组合(如“..”),例如,你说什么将允许一个名为“。txt”,我认为在Windows上无效的文件名。 因为这是最简单的方法,我会尝试从valid_chars中删除空格,并在错误的情况下预先设置一个已知的有效string,任何其他方法将不得不知道什么是允许在哪里处理Windows文件的命名限制 ,从而更复杂得多。
>>> 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'
什么是使用string作为文件名的原因? 如果人的可读性不是一个因素,我会去与base64模块,它可以产生文件系统安全的string。 它不可读,但你不必处理碰撞,它是可逆的。
import base64 file_name_string = base64.urlsafe_b64encode(your_string)
更新 :根据马修评论改变。
您可以将列表理解与string方法一起使用。
>>> s 'foo-bar#baz?qux@127/\\9]' >>> "".join(x for x in s if x.isalnum()) 'foobarbazqux1279'
只是为了进一步复杂的事情,你不能保证获得一个有效的文件名只是通过删除无效的字符。 由于允许的字符在不同的文件名上有所不同,保守的方法可能会导致有效的名称变为无效的名称。 您可能需要为以下情况添加特殊处理:
-
该string都是无效的字符(留下一个空string)
-
你最终得到一个有特殊意义的string,例如“。” 要么 ”..”
-
在窗口上, 某些设备名称被保留。 例如,你不能创build一个名为“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
您可能可以解决这些问题,方法是将一些string添加到永远不会导致这些情况之一的文件名中,并删除无效字符。
这是我最终使用的解决scheme:
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调用用不重叠的等价字符replace重音字符,这比简单地将其删除更好。 之后,所有不允许的字符被删除。
我的解决scheme不预先设置一个已知的string,以避免可能的不允许的文件名,因为我知道他们不能发生给我特定的文件名格式。 更一般的解决scheme将需要这样做。
Github上有一个名为python-slugify的好项目:
安装:
pip install python-slugify
然后使用:
>>> from slugify import slugify >>> txt = "This\ is/ a%#$ test ---" >>> slugify(txt) 'this-is-a-test'
请记住,Unix系统上的文件名实际上没有限制
- 它可能不包含\ 0
- 它可能不包含/
其他一切都是公平的游戏。
$ touch“ >甚至多行 >哈哈 > ^ [[31m红色^ [[0m >邪恶“ $ ls -la -rw-r - r-- 0十一月17 23:39?甚至多行?哈哈?? [31m红?[0m?恶 $ ls -lab - rw-r - r - 0 Nov 17 23:39 \ neven \ multiline \ nhaha \ n \ 033 [31m \ red \ \ 033 [0m \ nevil $ perl -e'为我$(glob(q {./ * even *})){print $ i; }' ./ 甚至多行 哈哈 红 邪恶
是的,我只是在文件名中存储ANSI颜色代码,并使其生效。
为了娱乐,把一个BEL字符放在一个目录名称中,并观看CD放入时的乐趣;)
>>> 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'
它不处理空string,特殊文件名('nul','con'等)。
为什么不只是用一个try / except包装“osopen”,并让底层操作系统排除文件是否有效?
这似乎less得多的工作,无论你使用的操作系统是有效的。
你可以使用re.sub()方法来取代任何不像“filelike”的东西。 但实际上,每个angular色都是有效的。 所以没有预先构build的function(我相信),完成它。
import re str = "File!name?.txt" f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))
会导致/tmp/filename.txt文件句柄。
虽然你必须小心。 如果你只是在看语言,那么在你的介绍中就没有清楚地说过。 有些单词可能变得毫无意义或其他含义,如果你只用ASCII字符消毒。
假设你有“森林诗歌”,你的消毒可能会给“堡垒”(强+有意义的东西)
更糟的是,如果你必须处理中文字符。
“下北沢”你的系统最终可能会做“—”,注定会在一段时间后失败,并不是很有帮助。 所以如果你只处理文件,我会鼓励把它们称为你控制的通用链,或者保持原来的字符。 对于URI,大致相同。
另一个问题,其他评论还没有解决的是空string,这显然不是一个有效的文件名。 你也可以用一个空string来剥离太多的字符。
什么与Windows保留的文件名和点的问题,“如何从任意用户input标准化一个有效的文件名?”这个问题的最安全的答案是“甚至不打扰尝试”:如果你能find其他的方法来避免它(例如使用来自数据库的整数主键作为文件名),那就这样做。
如果你必须的,你真的需要允许空格和'。 作为名称的一部分的文件扩展名,请尝试如下所示:
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操作系统讨厌空间和使用'。 作为目录分隔符。
在一行中:
valid_file_name = re.sub('[^\w_.)( -]', '', any_string)
你也可以使用'_'字符使其更具可读性(例如,在replace斜杠的情况下)
大多数这些解决scheme不起作用。
'/ hello / world' – >'helloworld'
'/ helloworld'/ – >'helloworld'
这是不是你想要的一般,如果你保存每个链接的HTML,你会覆盖不同的网页的HTML。
我腌制一个字典,如:
{'helloworld': ( {'/hello/world': 'helloworld', '/helloworld/': 'helloworld1'}, 2) }
2代表应附加到下一个文件名的数字。
每次从字典中查找文件名。 如果不存在,我创build一个新的,如果需要附加最大数量。
我相信这不是一个好的答案,因为它修改了它正在循环的string,但似乎工作正常:
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, '')
不是什么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+-_ .'))
结果是“有点”可读的,至less从系统pipe理员的angular度来看。
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'
原来的LINKROTTEN答案 :
bobcat
项目包含一个python模块,可以做到这一点。
这不是完全健壮的,看到这篇文章和这个答复 。
所以,如上所述:如果可读性不重要, base64
编码可能是一个更好的主意。
我喜欢这里的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
在这里)。
就像S.Lott的回答,你可以看看Django Framework如何将string转换为有效的文件名。
最新的和更新的版本可以在utils / text.py中find,并且定义了“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 )