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

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

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

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

Ответы [ 22 ]

7 голосов
/ 17 ноября 2008
>>> 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' и т. Д.).

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

Вы можете использовать метод re.sub (), чтобы заменить что-либо не "filelike". Но в действительности каждый персонаж может быть действительным; поэтому нет готовых функций (я полагаю), чтобы сделать это.

import re

str = "File!name?.txt"
f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))

Результатом будет дескриптор файла /tmp/filename.txt.

6 голосов
/ 11 марта 2009

Хотя вы должны быть осторожны. Это не ясно сказано в вашем вступлении, если вы смотрите только на латинский язык. Некоторые слова могут потерять смысл или другое значение, если вы очистите их только с помощью символов ascii.

представьте, что у вас есть "forêt poésie" (лесная поэзия), ваша дезинфекция может дать "fort-posie" (сильный + что-то бессмысленное)

Хуже, если вам приходится иметь дело с китайскими иероглифами.

"下 北 沢" ваша система может в конечном итоге выполнить "---", что обречено на провал через некоторое время и не очень полезно. Поэтому, если вы имеете дело только с файлами, я бы посоветовал назвать их общей цепочкой, которой вы управляете, или оставить символы такими, какие они есть. Для URI примерно одинаково.

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

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

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

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

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

Что с зарезервированными в 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 ненавидит пробелы и использует «.» В качестве разделителя каталогов.

2 голосов
/ 05 октября 2017

Мне понравился подход 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).

2 голосов
/ 16 мая 2012

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

'/ hello / world' -> 'helloworld'

'/ helloworld' / -> 'helloworld'

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

Я солёный, как:

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

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

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

1 голос
/ 12 сентября 2014

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

Результат "несколько" читабелен, по крайней мере, с точки зрения системного администратора.

0 голосов
/ 22 апреля 2019

Ответ изменен для python 3.6

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)
0 голосов
/ 11 марта 2019

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

...