У меня есть строка, которую я хочу использовать в качестве имени файла, поэтому я хочу удалить все символы, которые не были бы разрешены в именах файлов, используя Python.
Я бы предпочел быть строгим, чем иначе, поэтому предположим, что я хочу сохранить только буквы, цифры и небольшой набор других символов, таких как "_-.() ". Какое самое элегантное решение?
Имя файла должно быть действительным в нескольких операционных системах (Windows, Linux и Mac OS) - это файл MP3 в моей библиотеке с названием песни в качестве имени файла, который используется совместно и копируется между 3 машинами.
Возможно, хотя для ее варианта использования потребуется единственный путь, безопасный для платформ все, а не только текущий, для которого os.path не предназначен.
Чтобы расширить приведенный выше комментарий: текущий дизайн os.path фактически загружает другую библиотеку в зависимости от ОС (см. Второе примечание в документация). Таким образом, если в os.path была реализована функция заключения в кавычки, она могла заключать строку в кавычки только для обеспечения безопасности POSIX при работе в системе POSIX или для безопасности Windows при работе в Windows. Результирующее имя файла не обязательно будет действительным как для Windows, так и для POSIX, о чем и спрашивается.






Этот подход с использованием белого списка (т.е. разрешение только символов, присутствующих в valid_chars) будет работать, если нет ограничений на форматирование файлов или комбинацию допустимых символов, которые являются незаконными (например, ".."), например, что вы говорите разрешил бы имя файла с именем ". txt", которое, как мне кажется, недопустимо в Windows. Поскольку это самый простой подход, я бы попытался удалить пробелы из valid_chars и добавить известную действительную строку в случае ошибки, любой другой подход должен будет знать о том, что разрешено, где справиться с Ограничения именования файлов 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'
valid_chars = frozenset(valid_chars) не повредит. При применении ко всем символам это в 1,5 раза быстрее.
Предупреждение: это отображает две разные строки в одну и ту же строку >>> import string >>> valid_chars = "-. ()% s% s "% (string.ascii_letters, string.digits) >>> valid_chars '-. () AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567 89 '>>> filename = " a.com/hello/world ">>>' ' .join (c вместо c в имени файла, если c в valid_chars) 'a.comhelloworld' >>> filename = "a.com/helloworld" >>> '' .join (c вместо c в имени файла, если c в valid_chars) 'a .comhelloworld '>>>
Не говоря уже о том, что присвоение имени файлу "CON" в Windows доставит вам неприятности ...
Небольшая перестановка упрощает указание заменяющего символа. Сначала исходная функциональность: '' .join (c, если c в valid_chars else '' для c в имени файла) или с замененным символом или строкой для каждого недопустимого символа: '' .join (c, если c в valid_chars else '.' Для c в имени файла)
Незначительное замечание: ". Txt" - допустимое имя файла в Windows, хотя я подозреваю, что существует гораздо больше файлов ".gitignore".
Вы можете использовать метод re.sub () для замены чего-либо, кроме "файлового". Но на самом деле каждый символ мог быть действительным; так что нет никаких готовых функций (я считаю), чтобы это сделать.
import re
str = "File!name?.txt"
f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))
В результате будет дескриптор файла /tmp/filename.txt.
Вам нужно, чтобы тире было первым в сопоставлении групп, чтобы оно не отображалось как диапазон. re.sub ('[^ - a-zA-Z0-9 _. ()] +', '', str)
В чем причина использования строк в качестве имен файлов? Если удобочитаемость для человека не является фактором, я бы выбрал модуль base64, который может создавать строки, безопасные для файловой системы. Это не будет читаться, но вам не придется сталкиваться с коллизиями, и это обратимо.
import base64
file_name_string = base64.urlsafe_b64encode(your_string)
Обновлять: изменено на основании комментария Мэтью.
Предупреждение! Кодировка base64 по умолчанию включает в себя символ «/» как допустимый вывод, который недопустим в именах файлов во многих системах. Вместо этого используйте base64.urlsafe_b64encode (your_string)
Это должно рассматриваться как абсолютно как идеальный ответ для веб-серверов с любым внутренним пользовательским контентом. Даже если администратору нужно что-то найти, вы можете легко написать сценарий для преобразования всех запросов в одну и ту же форму.
На самом деле удобочитаемость почти всегда играет важную роль, даже если она используется только в целях отладки.
В Python 3 your_string должен быть байтовым массивом или результатом encode('ascii'), чтобы это работало.
def url2filename(url): url = url.encode('UTF-8') return base64.urlsafe_b64encode(url).decode('UTF-8') def filename2url(f): return base64.urlsafe_b64decode(f).decode('UTF-8')Вы можете использовать понимание списка вместе со строковыми методами.
>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'
Хотя он не включает несколько дополнительных символов, которые он хотел «_-. ()». Тем не менее, мое любимое решение;)
Обратите внимание, что квадратные скобки можно опустить. В этом случае для присоединения передается генератор выражения, что сохраняет этап создания неиспользуемого иначе списка.
+1 Очень понравилось. Небольшая модификация, которую я сделал: "" .join ([x if x.isalnum () else "_" for x in s]) - приведет к результату, в котором недопустимыми элементами являются _, как будто они пустые. Может быть, это кто-то другой.
Это отличное решение! Я сделал небольшую модификацию: filename = "".join(i for i in s if i not in "/:*?<>|")
К сожалению, здесь не допускаются даже пробелы и точки, но идея мне нравится.
@tiktak: чтобы (также) разрешить пробелы, точки и подчеркивания, вы можете использовать "".join( x for x in s if (x.isalnum() or x in "._- "))
@AlexKrycek, за исключением того, что вам нужно удвоить обратную косую черту.
Чтобы еще больше усложнить ситуацию, вам не гарантируется получение действительного имени файла, просто удалив недопустимые символы. Поскольку разрешенные символы различаются в разных именах файлов, консервативный подход может привести к превращению действительного имени в недопустимое. Вы можете добавить специальную обработку для случаев, когда:
Строка состоит из недопустимых символов (остается пустая строка)
В итоге получается строка со специальным значением, например, "." или же ".."
В окнах определенные имена устройств зарезервированы. Например, вы не можете создать файл с именем "nul", "nul.txt" (или nul. что угодно на самом деле). Зарезервированные имена:
CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8 и LPT9
Вы, вероятно, можете обойти эти проблемы, добавив некоторую строку к именам файлов, которые никогда не могут привести к одному из этих случаев, и удалив недопустимые символы.
>>> 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' и т. д.).
+1 для таблиц перевода, это, безусловно, самый эффективный метод. Для специальных имен файлов / пустых контейнеров будет достаточно простой проверки предварительных условий, а для посторонних периодов это также простое исправление.
Хотя перевод немного более эффективен, чем регулярное выражение, это время, скорее всего, будет меньше, если вы действительно попытаетесь открыть файл, что, несомненно, вы собираетесь сделать. Таким образом, я предпочитаю более читабельное решение с регулярным выражением, чем приведенный выше беспорядок.
Еще меня беспокоит черный список. Конечно, это черный список, основанный на белом списке, но все же. Это кажется менее ... безопасным. Откуда вы знаете, что "allchars" на самом деле завершено?
@isaaclw: '.translate ()' принимает строку из 256 символов в качестве таблицы перевода (побайтовое преобразование). '.maketrans ()' создает такую строку. Все ценности покрыты; это чистый белый список подход
А как насчет имени файла '.' (одна точка). Это не сработает в Unix, поскольку текущий каталог использует это имя.
Имейте в виду, что на самом деле нет никаких ограничений на имена файлов в системах Unix, кроме
Все остальное - честная игра.
$ 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;)
OP заявляет, что «Имя файла должно быть действительным в нескольких операционных системах».
@cowlinator, что пояснение было добавлено через 10 часов после публикации моего ответа :) Проверьте журнал редактирования OP.
Почему бы просто не обернуть «osopen» с помощью try / except и позволить базовой ОС разобраться, действителен ли файл?
Это похоже на гораздо меньшую работу и действительно независимо от того, какую ОС вы используете.
Правда ли это имя? Я имею в виду, что если ОС не устраивает, то все равно нужно что-то делать, не так ли?
В некоторых случаях OS / Language может незаметно преобразовать ваше имя файла в альтернативную форму, но когда вы сделаете список каталогов, вы получите другое имя. И это может привести к проблеме «когда я пишу файл, но когда я ищу файл, он вызывает что-то еще». (Я говорю о поведении, о котором слышал на VAX ...)
Более того, «Имя файла должно быть действительным в нескольких операционных системах», что вы не можете обнаружить, если osopen работает на одной машине.
Вы можете посмотреть на Фреймворк Django, как они создают «слаг» из произвольного текста. Слаг удобен для URL и файлов.
Текстовые утилиты Django определяют функцию 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
Есть еще кое-что, но я не упомянул об этом, так как он не касается слагов, а убегает.
Последняя строка должна быть: value = unicode (re.sub ('[- \ s] +', '-', value))
Спасибо - я мог что-то упустить, но получаю: «аргумент normalize () 2 должен быть в Юникоде, а не в str»
"normalize () аргумент 2". Имеется ввиду value. Если значение должно быть Unicode, тогда вы должны быть уверены, что это действительно Unicode. Или же. Возможно, вы захотите опустить нормализацию Unicode, если ваше фактическое значение на самом деле является строкой ASCII.
В случае, если кто-то не заметил положительной стороны этого подхода, является то, что он не просто удаляет не-альфа-символы, но пытается сначала найти хорошие заменители (через нормализацию NFKD), поэтому é становится e, верхний индекс 1 становится нормальный 1 и т.д. Спасибо
Для не веб-приложения. Я переписал функцию, чтобы сохранить немецкие (и другие не-ascii) буквы как есть: re_slugify = re.compile ('[^ \ w \ s-]', re.UNICODE) def slugify (value): value = unicodedata .normalize ('NFKD', value) value = unicode (re_slugify.sub ('', value) .strip ()) value = re.sub ('[- \ s] +', '-', value) возвращаемое значение
Функция slugify перемещена в django / utils / text.py, и этот файл также содержит функцию get_valid_filename.
Я счел полезным заменить пробелы на "-"
затем добавьте return value в конце
Функция slugify (версия Python 3) доступна по адресу github.com/django/django/blob/master/django/utils/text.py
Другая проблема, которую еще не рассмотрели другие комментарии, - это пустая строка, которая, очевидно, не является допустимым именем файла. Вы также можете получить пустую строку из-за удаления слишком большого количества символов.
Что с зарезервированными именами файлов 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 ненавидит пробелы и использует "." В качестве разделителя каталогов.
Хотя надо быть осторожным. Это не ясно сказано в вашем вступлении, если вы смотрите только на латинском языке. Некоторые слова могут стать бессмысленными или иметь другое значение, если вы дезинфицируете их только с помощью символов ascii.
представьте, что у вас есть «forêt poésie» (лесная поэзия), ваша дезинфекция может дать «fort-posie» (сильный + что-то бессмысленное)
Хуже, если вам приходится иметь дело с китайскими иероглифами.
«下 北 沢» ваша система может в конечном итоге выполнить «---», что обречено на сбой через некоторое время и не очень помогает. Поэтому, если вы имеете дело только с файлами, я бы посоветовал либо назвать их общей цепочкой, которую вы контролируете, либо оставить символы как есть. Для URI примерно то же самое.
Это решение, которое я в конечном итоге использовал:
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 заменяет символы с диакритическими знаками их эквивалентами без акцента, что лучше, чем просто их убирать. После этого все запрещенные символы удаляются.
Мое решение не добавляет известную строку, чтобы избежать возможных запрещенных имен файлов, потому что я знаю, что они не могут возникнуть с учетом моего конкретного формата имени файла. Для этого потребуется более общее решение.
вы должны иметь возможность использовать uuid.uuid4 () для своего уникального префикса
дело верблюда .. ааа
Можно ли отредактировать / обновить его для работы с Python 3.6?
ОБНОВИТЬ
Все ссылки не подлежат ремонту в этом ответе 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'
ОРИГИНАЛЬНЫЙ ОТВЕТ С ЗАЩИТНЫМИ ССЫЛКАМИ:
Проект bobcat содержит модуль python, который делает именно это.
Это не совсем надежно, см. Этот Почта и этот Ответить.
Итак, как уже отмечалось: кодирование base64, вероятно, является лучшей идеей, если читаемость не имеет значения.
Все ссылки мертвы. Мужик, сделай что-нибудь.
Я уверен, что это не лучший ответ, поскольку он изменяет строку, которую он перебирает, но, похоже, работает нормально:
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, '')
Я нашел этот "".join( x for x in s if (x.isalnum() or x in "._- ")) в комментариях к Почта
Большинство из этих решений не работают.
'/ привет / мир' -> 'привет мир'
'/ helloworld' / -> 'helloworld'
Обычно это не то, что вам нужно, скажем, вы сохраняете html для каждой ссылки, вы собираетесь перезаписать html для другой веб-страницы.
Я выбираю такой изречение, как:
{'helloworld':
(
{'/hello/world': 'helloworld', '/helloworld/': 'helloworld1'},
2)
}
2 представляет собой число, которое должно быть добавлено к следующему имени файла.
Я каждый раз ищу имя файла в диктовке. Если его там нет, я создаю новый, добавляя при необходимости максимальное число.
обратите внимание, если вы используете helloworld1, вам также необходимо проверить, что helloworld1 не используется и т. д.
Не совсем то, о чем просил 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+-_ .'))
Результат "в некоторой степени" читабелен, по крайней мере, с точки зрения системного администратора.
Обертка для этого без пробелов в именах файлов: def safe_filename(filename): return safePath(filename.strip().replace(' ','_'))
На Github есть хороший проект под названием python-slugify:
Установить:
pip install python-slugify
Затем используйте:
>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'
Мне нравится эта библиотека, но она не так хороша, как я думал. Начальное тестирование в порядке, но оно также преобразует точки. Итак, test.txt получает test-txt, что слишком много.
В одной строке:
valid_file_name = re.sub('[^\w_.)( -]', '', any_string)
вы также можете поставить символ '_', чтобы сделать его более читаемым (например, в случае замены косой черты)
Мне понравился подход 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).
Мне это нравится, не изобретайте велосипед, не импортируйте всю структуру Django, если она вам не нужна, не вставляйте код напрямую, если вы не собираетесь поддерживать его в будущем, и сгенерированная строка пытается чтобы сопоставить похожие буквы с безопасными, чтобы новую строку было легче читать.
Чтобы использовать подчеркивание вместо тире: name = slugify (s, separator = '_')
Так же, как ответил С.Лотт, вы можете посмотреть на 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)
для ленивых уже на django: django.utils.text import get_valid_filename
Если вы не знакомы с регулярным выражением, re.sub(r'(?u)[^-\w.]', '', s) удаляет все символы, которые не являются буквами, цифрами (0-9), подчеркиванием ('_'), тире ('-') и точкой ('. '). «Буквы» здесь включают все буквы юникода, например.
Вы также можете проверить длину: имена файлов ограничены 255 символами (или, вы знаете, 32; в зависимости от FS)
return re.sub(r'(?u)[^-\w.]', '_', s) для лучшей читаемости
Я понимаю, что есть много ответов, но они в основном полагаются на регулярные выражения или внешние модули, поэтому я хотел бы добавить свой собственный ответ. Чистая функция 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, включив в него разделитель путей в ОС.
Ответ изменен для Python 3.6
import string
import unicodedata
validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
def removeDisallowedFilenameChars(filename):
cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
return ''.join(chr(c) for c in cleanedFilename if chr(c) in validFilenameChars)
Не могли бы вы подробно объяснить свой ответ?
Такой же ответ приняла Софи Гейдж. Но он был изменен для работы на python 3.6.
Если вы не возражаете против установки пакета, это может быть полезно: https://pypi.org/project/pathvalidate/
От https://pypi.org/project/pathvalidate/#sanitize-a-filename:
from pathvalidate import sanitize_filename fname = "fi:l*e/p\"a?t>h|.t<xt" print(f"{fname} -> {sanitize_filename(fname)}\n") fname = "\0_a*b:c<d>e%f/(g)h+i_0.txt" print(f"{fname} -> {sanitize_filename(fname)}\n")Output
fi:l*e/p"a?t>h|.t<xt -> filepath.txt _a*b:c<d>e%f/(g)h+i_0.txt -> _abcde%f(g)h+i_0.txt
Здесь это должно охватывать все основы. Он решает все типы проблем, включая (но не ограничиваясь) замену символов.
Работает в Windows, * nix и почти во всех других файловых системах. Допускает только печатные символы.
def txt2filename(txt, chr_set='normal'):
"""Converts txt to a valid Windows/*nix filename with printable characters only.
args:
txt: The str to convert.
chr_set: 'normal', 'universal', or 'inclusive'.
'universal': ' -.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
'normal': Every printable character exept those disallowed on Windows/*nix.
'extended': All 'normal' characters plus the extended character ASCII codes 128-255
"""
FILLER = '-'
# Step 1: Remove excluded characters.
if chr_set == 'universal':
# Lookups in a set are O(n) vs O(n * x) for a str.
printables = set(' -.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
else:
if chr_set == 'normal':
max_chr = 127
elif chr_set == 'extended':
max_chr = 256
else:
raise ValueError(f'The chr_set argument may be normal, extended or universal; not {chr_set=}')
EXCLUDED_CHRS = set(r'<>:"/\|?*') # Illegal characters in Windows filenames.
EXCLUDED_CHRS.update(chr(127)) # DEL (non-printable).
printables = set(chr(x)
for x in range(32, max_chr)
if chr(x) not in EXCLUDED_CHRS)
result = ''.join(x if x in printables else FILLER # Allow printable characters only.
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'-{result}-'
# Step 3: Maximum length of filename is 255 bytes in Windows and Linux (other *nix flavors may allow longer names).
result = result[:255]
# 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
Это решение не требует внешних библиотек. Он также заменяет непечатаемые имена файлов, потому что с ними не всегда просто иметь дело.
Столкнувшись с той же проблемой, я использовал python-slugify.
Использование было также предложено Шохамом, но, как указал Терелмарв, по умолчанию 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
глобальная переменная в файле slugify.py пакета python-slugify и дополнена символом "."
Имейте в виду, что специальные символы, такие как .(), должны быть экранированы с помощью \.
Если вы хотите сохранить прописные буквы, используйте аргумент lowercase=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.
Разве это не должно быть встроено в модуль os.path?