Превратить строку в правильное имя файла? - PullRequest
255 голосов
/ 17 ноября 2008

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

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

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

Ответы [ 22 ]

138 голосов
/ 17 ноября 2008

Вы можете взглянуть на Django framework , чтобы узнать, как они создают «слаг» из произвольного текста. Плагин подходит для URL и имен файлов.

Текстовые утилиты Django определяют функцию, 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))

Есть еще кое-что, но я не упомянул об этом, так как это не касается слизи, а спасения.

97 голосов
/ 17 ноября 2008

Этот подход с использованием белого списка (т. Е. Разрешающий только символы, присутствующие в 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'
93 голосов
/ 17 ноября 2008

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

>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'
89 голосов
/ 17 ноября 2008

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

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

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

37 голосов
/ 17 ноября 2008

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

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

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

  • В Windows определенные имена устройств зарезервированы. Например, вы не можете создать файл с именем «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

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

22 голосов
/ 29 апреля 2015

На Github есть хороший проект под названием python-slugify :

Установка:

pip install python-slugify

Тогда используйте:

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'
18 голосов
/ 30 марта 2009

Это решение, которое я в конечном итоге использовал:

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

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

17 голосов
/ 18 октября 2017

Точно так же, как ответил 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)

13 голосов
/ 17 ноября 2008

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

8 голосов
/ 04 августа 2016

В одну строку:

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

вы также можете поставить символ '_', чтобы сделать его более читабельным (например, в случае замены слеша)

...