Превратить строку в допустимое имя файла?

У меня есть строка, которую я хочу использовать в качестве имени файла, поэтому я хочу удалить все символы, которые не были бы разрешены в именах файлов, используя Python.

Я бы предпочел быть строгим, чем иначе, поэтому предположим, что я хочу сохранить только буквы, цифры и небольшой набор других символов, таких как "_-.() ". Какое самое элегантное решение?

Имя файла должно быть действительным в нескольких операционных системах (Windows, Linux и Mac OS) - это файл MP3 в моей библиотеке с названием песни в качестве имени файла, который используется совместно и копируется между 3 машинами.

Разве это не должно быть встроено в модуль os.path?

endolith 10.03.2009 16:59

Возможно, хотя для ее варианта использования потребуется единственный путь, безопасный для платформ все, а не только текущий, для которого os.path не предназначен.

javawizard 04.06.2013 01:37

Чтобы расширить приведенный выше комментарий: текущий дизайн os.path фактически загружает другую библиотеку в зависимости от ОС (см. Второе примечание в документация). Таким образом, если в os.path была реализована функция заключения в кавычки, она могла заключать строку в кавычки только для обеспечения безопасности POSIX при работе в системе POSIX или для безопасности Windows при работе в Windows. Результирующее имя файла не обязательно будет действительным как для Windows, так и для POSIX, о чем и спрашивается.

dshepherd 10.03.2015 13:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
330
3
192 913
25
Перейти к ответу Данный вопрос помечен как решенный

Ответы 25

Этот подход с использованием белого списка (т.е. разрешение только символов, присутствующих в 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 раза быстрее.
jfs 17.11.2008 14:14

Предупреждение: это отображает две разные строки в одну и ту же строку >>> 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 '>>>

robert king 16.05.2012 04:53

Не говоря уже о том, что присвоение имени файлу "CON" в Windows доставит вам неприятности ...

Nathan Osman 05.07.2012 08:09

Небольшая перестановка упрощает указание заменяющего символа. Сначала исходная функциональность: '' .join (c, если c в valid_chars else '' для c в имени файла) или с замененным символом или строкой для каждого недопустимого символа: '' .join (c, если c в valid_chars else '.' Для c в имени файла)

PeterVermont 10.02.2014 23:17

Незначительное замечание: ". Txt" - допустимое имя файла в Windows, хотя я подозреваю, что существует гораздо больше файлов ".gitignore".

Pat Corwin 07.09.2020 07:57

Вы можете использовать метод 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)

phord 16.11.2010 03:09

В чем причина использования строк в качестве имен файлов? Если удобочитаемость для человека не является фактором, я бы выбрал модуль base64, который может создавать строки, безопасные для файловой системы. Это не будет читаться, но вам не придется сталкиваться с коллизиями, и это обратимо.

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

Обновлять: изменено на основании комментария Мэтью.

Предупреждение! Кодировка base64 по умолчанию включает в себя символ «/» как допустимый вывод, который недопустим в именах файлов во многих системах. Вместо этого используйте base64.urlsafe_b64encode (your_string)

Matthew 12.04.2009 05:33

Это должно рассматриваться как абсолютно как идеальный ответ для веб-серверов с любым внутренним пользовательским контентом. Даже если администратору нужно что-то найти, вы можете легко написать сценарий для преобразования всех запросов в одну и ту же форму.

codetaku 19.10.2013 00:49

На самом деле удобочитаемость почти всегда играет важную роль, даже если она используется только в целях отладки.

static_rtti 25.06.2015 11:46

В Python 3 your_string должен быть байтовым массивом или результатом encode('ascii'), чтобы это работало.

Noumenon 25.07.2015 21:43
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')
JeffProd 15.12.2015 17:44

Вы можете использовать понимание списка вместе со строковыми методами.

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

Хотя он не включает несколько дополнительных символов, которые он хотел «_-. ()». Тем не менее, мое любимое решение;)

matt burns 31.01.2011 12:06

Обратите внимание, что квадратные скобки можно опустить. В этом случае для присоединения передается генератор выражения, что сохраняет этап создания неиспользуемого иначе списка.

Oben Sonne 17.10.2011 15:25

+1 Очень понравилось. Небольшая модификация, которую я сделал: "" .join ([x if x.isalnum () else "_" for x in s]) - приведет к результату, в котором недопустимыми элементами являются _, как будто они пустые. Может быть, это кто-то другой.

Eddie Parker 07.12.2012 02:44

Это отличное решение! Я сделал небольшую модификацию: filename = "".join(i for i in s if i not in "/:*?<>|")

Alex Krycek 24.06.2013 03:17

К сожалению, здесь не допускаются даже пробелы и точки, но идея мне нравится.

tiktak 11.07.2013 14:40

@tiktak: чтобы (также) разрешить пробелы, точки и подчеркивания, вы можете использовать "".join( x for x in s if (x.isalnum() or x in "._- "))

hardmooth 09.05.2016 09:32

@AlexKrycek, за исключением того, что вам нужно удвоить обратную косую черту.

Michel de Ruiter 19.11.2020 12:45

Чтобы еще больше усложнить ситуацию, вам не гарантируется получение действительного имени файла, просто удалив недопустимые символы. Поскольку разрешенные символы различаются в разных именах файлов, консервативный подход может привести к превращению действительного имени в недопустимое. Вы можете добавить специальную обработку для случаев, когда:

  • Строка состоит из недопустимых символов (остается пустая строка)

  • В итоге получается строка со специальным значением, например, "." или же ".."

  • В окнах определенные имена устройств зарезервированы. Например, вы не можете создать файл с именем "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 для таблиц перевода, это, безусловно, самый эффективный метод. Для специальных имен файлов / пустых контейнеров будет достаточно простой проверки предварительных условий, а для посторонних периодов это также простое исправление.

Christian Witts 11.03.2009 13:59

Хотя перевод немного более эффективен, чем регулярное выражение, это время, скорее всего, будет меньше, если вы действительно попытаетесь открыть файл, что, несомненно, вы собираетесь сделать. Таким образом, я предпочитаю более читабельное решение с регулярным выражением, чем приведенный выше беспорядок.

nosatalian 23.12.2009 22:21

Еще меня беспокоит черный список. Конечно, это черный список, основанный на белом списке, но все же. Это кажется менее ... безопасным. Откуда вы знаете, что "allchars" на самом деле завершено?

isaaclw 24.04.2012 22:23

@isaaclw: '.translate ()' принимает строку из 256 символов в качестве таблицы перевода (побайтовое преобразование). '.maketrans ()' создает такую ​​строку. Все ценности покрыты; это чистый белый список подход

jfs 25.04.2012 11:48

А как насчет имени файла '.' (одна точка). Это не сработает в Unix, поскольку текущий каталог использует это имя.

Finn Årup Nielsen 04.02.2015 19:26

Имейте в виду, что на самом деле нет никаких ограничений на имена файлов в системах 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;)

OP заявляет, что «Имя файла должно быть действительным в нескольких операционных системах».

cowlinator 09.07.2018 23:18

@cowlinator, что пояснение было добавлено через 10 часов после публикации моего ответа :) Проверьте журнал редактирования OP.

Kent Fredric 16.05.2019 04:08

Почему бы просто не обернуть «osopen» с помощью try / except и позволить базовой ОС разобраться, действителен ли файл?

Это похоже на гораздо меньшую работу и действительно независимо от того, какую ОС вы используете.

Правда ли это имя? Я имею в виду, что если ОС не устраивает, то все равно нужно что-то делать, не так ли?

jeromej 27.02.2014 05:33

В некоторых случаях OS / Language может незаметно преобразовать ваше имя файла в альтернативную форму, но когда вы сделаете список каталогов, вы получите другое имя. И это может привести к проблеме «когда я пишу файл, но когда я ищу файл, он вызывает что-то еще». (Я говорю о поведении, о котором слышал на VAX ...)

Kent Fredric 05.04.2016 13:22

Более того, «Имя файла должно быть действительным в нескольких операционных системах», что вы не можете обнаружить, если osopen работает на одной машине.

LarsH 04.06.2019 15:15
Ответ принят как подходящий

Вы можете посмотреть на Фреймворк 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))

Joseph Turian 08.10.2010 10:49

Спасибо - я мог что-то упустить, но получаю: «аргумент normalize () 2 должен быть в Юникоде, а не в str»

Alex Cook 12.01.2012 23:21

"normalize () аргумент 2". Имеется ввиду value. Если значение должно быть Unicode, тогда вы должны быть уверены, что это действительно Unicode. Или же. Возможно, вы захотите опустить нормализацию Unicode, если ваше фактическое значение на самом деле является строкой ASCII.

S.Lott 12.01.2012 23:27

В случае, если кто-то не заметил положительной стороны этого подхода, является то, что он не просто удаляет не-альфа-символы, но пытается сначала найти хорошие заменители (через нормализацию NFKD), поэтому é становится e, верхний индекс 1 становится нормальный 1 и т.д. Спасибо

Michael Scott Cuthbert 08.11.2012 06:13

Для не веб-приложения. Я переписал функцию, чтобы сохранить немецкие (и другие не-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) возвращаемое значение

olpa 10.10.2013 13:03

Функция slugify перемещена в django / utils / text.py, и этот файл также содержит функцию get_valid_filename.

Denilson Sá Maia 04.12.2013 01:39

Я счел полезным заменить пробелы на "-"

Claudiu 08.08.2016 01:36

затем добавьте return value в конце

am70 27.10.2019 10:50

Функция slugify (версия Python 3) доступна по адресу github.com/django/django/blob/master/django/utils/text.py

am70 27.10.2019 10:59

Другая проблема, которую еще не рассмотрели другие комментарии, - это пустая строка, которая, очевидно, не является допустимым именем файла. Вы также можете получить пустую строку из-за удаления слишком большого количества символов.

Что с зарезервированными именами файлов 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 () для своего уникального префикса

slf 30.03.2009 23:50

дело верблюда .. ааа

demented hedgehog 17.10.2016 06:54

Можно ли отредактировать / обновить его для работы с Python 3.6?

Wavesailor 02.03.2018 18:23

ОБНОВИТЬ

Все ссылки не подлежат ремонту в этом ответе 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, вероятно, является лучшей идеей, если читаемость не имеет значения.

Все ссылки мертвы. Мужик, сделай что-нибудь.

Shiva 26.12.2015 13:18

Я уверен, что это не лучший ответ, поскольку он изменяет строку, которую он перебирает, но, похоже, работает нормально:

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 "._- ")) в комментариях к Почта

SergioAraujo 30.05.2018 17:10

Большинство из этих решений не работают.

'/ привет / мир' -> 'привет мир'

'/ helloworld' / -> 'helloworld'

Обычно это не то, что вам нужно, скажем, вы сохраняете html для каждой ссылки, вы собираетесь перезаписать html для другой веб-страницы.

Я выбираю такой изречение, как:

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

2 представляет собой число, которое должно быть добавлено к следующему имени файла.

Я каждый раз ищу имя файла в диктовке. Если его там нет, я создаю новый, добавляя при необходимости максимальное число.

обратите внимание, если вы используете helloworld1, вам также необходимо проверить, что helloworld1 не используется и т. д.

robert king 26.11.2013 01:21

Не совсем то, о чем просил 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(' ','_'))

SpeedCoder5 20.06.2018 20:23

На 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, что слишком много.

therealmarv 05.10.2017 19:05

В одной строке:

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, если она вам не нужна, не вставляйте код напрямую, если вы не собираетесь поддерживать его в будущем, и сгенерированная строка пытается чтобы сопоставить похожие буквы с безопасными, чтобы новую строку было легче читать.

vicenteherrera 23.03.2020 15:15

Чтобы использовать подчеркивание вместо тире: name = slugify (s, separator = '_')

vicenteherrera 23.03.2020 15:19

Так же, как ответил С.Лотт, вы можете посмотреть на 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

theannouncer 02.03.2018 22:59

Если вы не знакомы с регулярным выражением, re.sub(r'(?u)[^-\w.]', '', s) удаляет все символы, которые не являются буквами, цифрами (0-9), подчеркиванием ('_'), тире ('-') и точкой ('. '). «Буквы» здесь включают все буквы юникода, например.

cowlinator 05.05.2018 00:41

Вы также можете проверить длину: имена файлов ограничены 255 символами (или, вы знаете, 32; в зависимости от FS)

Matthias Winkelmann 13.11.2018 15:50
return re.sub(r'(?u)[^-\w.]', '_', s) для лучшей читаемости
spiralmoon 05.07.2020 21:21

Я понимаю, что есть много ответов, но они в основном полагаются на регулярные выражения или внешние модули, поэтому я хотел бы добавить свой собственный ответ. Чистая функция 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)

Не могли бы вы подробно объяснить свой ответ?

Serenity 22.04.2019 08:07

Такой же ответ приняла Софи Гейдж. Но он был изменен для работы на python 3.6.

Jean-Robin Tremblay 22.04.2019 08:23

Если вы не возражаете против установки пакета, это может быть полезно: 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.

Другие вопросы по теме