369

ファイル名として使用したい文字列があるので、Python を使用して、ファイル名に使用できないすべての文字を削除したいと考えています。

他の方法よりも厳密にしたいので、文字、数字、および"_-.() ". 最もエレガントなソリューションは何ですか?

ファイル名は、複数のオペレーティング システム (Windows、Linux、および Mac OS) で有効である必要があります。これは、ライブラリ内の MP3 ファイルであり、曲のタイトルがファイル名として使用され、3 台のマシン間で共有およびバックアップされます。

4

26 に答える 26

221

任意のテキストから「スラッグ」を作成する方法については、Django フレームワークを参照してください。slug は、URL とファイル名に適しています。

Django テキスト utils は、関数 を定義します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

他にもありますが、スラッジ化ではなくエスケープであるため、省略しました。

于 2008-11-17T12:23:52.690 に答える
140

リスト内包表記は文字列メソッドと一緒に使用できます。

>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'
于 2008-11-17T09:12:49.910 に答える
108

このホワイトリスト アプローチ (つまり、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'
于 2008-11-17T09:10:49.507 に答える
107

文字列をファイル名として使用する理由は何ですか? 人間の可読性が要因でない場合は、ファイル システムの安全な文字列を生成できる base64 モジュールを使用します。読み取りはできませんが、衝突に対処する必要はなく、可逆的です。

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

更新: Matthew のコメントに基づいて変更されました。

于 2008-11-17T09:12:02.110 に答える
43

Github にはpython-slugifyという素敵なプロジェクトがあります:

インストール:

pip install python-slugify

次に使用します。

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'
于 2015-04-29T11:19:47.043 に答える
43

さらに複雑なことに、無効な文字を削除しただけでは有効なファイル名が得られる保証はありません。許可される文字はファイル名によって異なるため、保守的なアプローチでは有効な名前が無効な名前になってしまう可能性があります。次の場合には、特別な処理を追加することができます。

  • 文字列はすべて無効な文字です (空の文字列が残ります)

  • 「.」などの特別な意味を持つ文字列になります。また ".."

  • 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

これらのケースのいずれにもなり得ないファイル名の先頭に文字列を追加し、無効な文字を削除することで、これらの問題を回避できる可能性があります。

于 2008-11-17T09:57:40.240 に答える
21

これは私が最終的に使用したソリューションです:

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 呼び出しは、アクセント付きの文字をアクセントのない同等の文字に置き換えます。これは、単にそれらを取り除くよりも優れています。その後、許可されていないすべての文字が削除されます。

私のソリューションでは、許可されていない可能性のあるファイル名を回避するために既知の文字列を追加しません。これは、特定のファイル名形式では発生しないことがわかっているためです。より一般的な解決策はそうする必要があります。

于 2009-03-30T19:40:17.967 に答える
15

Unixシステムでは、実際にはファイル名に制限はありません。

  • \0が含まれていない可能性があります
  • /を含まない場合があります

他のすべては公正なゲームです。

$ touch "
>マルチラインでも
>ハハ
>^[[31m赤^[[0m
>悪」
$ ls -la
-rw-r--r-- 0 Nov 17 23:39?マルチラインでも?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; }'
./
マルチラインでも
はは
 赤
悪の

はい、ANSIカラーコードをファイル名に保存して有効にしました。

娯楽のために、ディレクトリ名にベル文字を入れて、それにCDを入れるときに続く楽しみを見てください;)

于 2008-11-17T10:45:54.277 に答える
7
>>> 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' など) は処理しません。

于 2008-11-17T10:15:15.290 に答える
7

re.sub() メソッドを使用して、「ファイル風」ではないものを置き換えることができます。しかし実際には、すべての文字が有効である可能性があります。そのため、それを実行するための事前構築済みの関数はありません (私は信じています)。

import re

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

/tmp/filename.txt へのファイルハンドルになります。

于 2008-11-17T09:10:53.640 に答える
6

「osopen」をtry/exceptでラップして、基盤となるOSにファイルが有効かどうかを分類させてみませんか?

これは作業がはるかに少ないようで、使用するOSに関係なく有効です。

于 2008-11-17T11:24:49.690 に答える
6

他のコメントがまだ対処していないもう 1 つの問題は、空の文字列です。これは明らかに有効なファイル名ではありません。また、あまりにも多くの文字を削除すると、空の文字列になる可能性があります。

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

特に予期しない OS では、これでさえ正しく保証されません。たとえば、RISC OS はスペースを嫌い、「.」を使用します。ディレクトリ区切りとして。

于 2008-11-17T13:24:19.840 に答える
5

私はここで 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こちら)。

于 2017-10-05T16:36:47.090 に答える
2

同じ問題に直面したとき、私は python-slugify を使用しました。

使用法も Shoham によって提案されましたが、therealmarv が指摘したように、デフォルトでは 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.pypython-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 を使用して機能しました

于 2021-03-26T08:15:03.323 に答える
1

これらのソリューションのほとんどは機能しません。

「/hello/world」 -> 「helloworld」

'/ハローワールド'/ -> 'ハローワールド'

これは、通常、各リンクの html を保存している場合、別の Web ページの html を上書きすることを望んでいるものではありません。

次のような辞書をピクルします。

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

2 は、次のファイル名に追加する番号を表します。

辞書から毎回ファイル名を調べます。そこにない場合は、必要に応じて最大数を追加して、新しいものを作成します。

于 2012-05-16T01:04:34.197 に答える
0

ループしている文字列を変更するため、これは素晴らしい答えではないと確信していますが、問題なく動作するようです:

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, '')
于 2012-05-05T03:56:00.797 に答える
0

アップデート

この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エンコードとデコードができるので、元のファイル名を再び取得できます。

ただし、ユースケースによっては、ランダムなファイル名を生成し、メタデータを別のファイルまたは DB に保存する方がよい場合があります。

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、読みやすさが問題にならない場合は、エンコーディングがおそらくより良い考えです。

于 2009-07-10T10:19:11.657 に答える