Frage

Ich habe eine Zeichenfolge, die ich als Dateinamen verwenden möchte, daher möchte ich mit Python alle Zeichen entfernen, die in Dateinamen nicht zulässig wären.

Ich möchte lieber streng sein als anders. Nehmen wir also an, ich möchte nur Buchstaben, Ziffern und einen kleinen Satz anderer Zeichen beibehalten "_-.() ".Was ist die eleganteste Lösung?

Der Dateiname muss auf mehreren Betriebssystemen (Windows, Linux und Mac OS) gültig sein – es handelt sich um eine MP3-Datei in meiner Bibliothek mit dem Songtitel als Dateinamen und wird von drei Computern gemeinsam genutzt und gesichert.

War es hilfreich?

Lösung 7

Dies ist die Lösung, die ich letztlich verwendet:

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)

Der unicodedata.normalize Aufruf ersetzt mit tonloser äquivalenten Zeichen mit Akzent, die als einfach besser ist sie Strippen. Danach werden alle unzulässigen Zeichen entfernt werden.

Meine Lösung keinen bekannten String prepend möglich unzulässige Dateinamen zu vermeiden, weil ich weiß, können sie nicht mein speziellen Dateinamen Format gegeben auftreten. Eine allgemeine Lösung müßte dies tun.

Andere Tipps

Sie können sehen Sie den Django Framework , wie sie eine „Schnecke“ aus beliebigem Text erstellen. Eine Schnecke ist URL- und filename- freundlich.

Der Django Text utils definiert eine Funktion, slugify() , das ist wahrscheinlich der Goldstandard für diese Art der Sache. Im Wesentlichen ihr Code ist die folgende.

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))

Es gibt noch mehr, aber ich habe es aus, da es slugification nicht behandelt, sondern entweicht.

Dieses Whitelist-Ansatz (dh, so dass nur die Zeichen in valid_chars) wird funktionieren, wenn es keine Grenzen für die Formatierung der Dateien oder eine Kombination von gültigen Zeichen sind, die illegal sind (wie „..“), zum Beispiel, was sagen Sie einen Dateinamen erlauben würde, den Namen „txt“, die ich denke nicht unter Windows gültig ist. Da dies der einfachste Ansatz ist, ich würde versuchen, Leerzeichen aus dem valid_chars zu entfernen und eine bekannte gültige Zeichenfolge im Fehlerfall Zierer anderer Ansatz müssen wissen, was erlaubt ist, wo mit Windows Dateibenennung Einschränkungen und damit viel komplexer sein .

>>> 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'

Sie können die Liste Verständnis verwenden zusammen mit den String-Methoden.

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

Was ist der Grund, die Zeichenfolge als Dateinamen zu benutzen? Wenn die menschliche Lesbarkeit keinen Faktor ist, würde ich mit Base64-Modul geht das Dateisystem sicher Strings erzeugen kann. Es wird nicht lesbar sein, aber Sie müssen nicht mit Kollisionen umgehen und es ist reversibel.

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

Aktualisieren :. Changed basiert auf Matthew Kommentar

Um die Sache noch komplizierter zu machen, kann nicht garantiert werden, dass Sie einen gültigen Dateinamen erhalten, indem Sie einfach ungültige Zeichen entfernen.Da die zulässigen Zeichen in verschiedenen Dateinamen unterschiedlich sind, könnte ein konservativer Ansatz dazu führen, dass ein gültiger Name ungültig wird.Möglicherweise möchten Sie eine spezielle Behandlung für die folgenden Fälle hinzufügen:

  • Die Zeichenfolge besteht ausschließlich aus ungültigen Zeichen (es bleibt eine leere Zeichenfolge übrig)

  • Sie haben eine Zeichenfolge mit einer besonderen Bedeutung, z. B. "." oder ".."

  • An Fenstern, bestimmte Gerätenamen sind vorbehalten.Sie können beispielsweise keine Datei mit dem Namen „nul“, „nul.txt“ (oder eigentlich nul.irgendetwas) erstellen. Die reservierten Namen sind:

    CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8 und LPT9

Sie können diese Probleme wahrscheinlich umgehen, indem Sie den Dateinamen eine Zeichenfolge voranstellen, die niemals zu einem dieser Fälle führen kann, und ungültige Zeichen entfernen.

Es gibt ein schönes Projekt auf Github genannt python-slugify :

Installieren:

pip install python-slugify

Dann verwenden:

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'

Wie S.Lott beantwortet , können Sie auf der Django Framework-, wie sie einen String in einen gültigen Dateinamen umwandeln.

Die jüngste und aktualisierte Version ist in utils / text.py gefunden und definiert "get_valid_filename", die sich wie folgt:

def get_valid_filename(s):
    s = str(s).strip().replace(' ', '_')
    return re.sub(r'(?u)[^-\w.]', '', s)

(Siehe https://github.com/django/ django / Blob / Master / django / utils / text.py )

Halten Sie

im Auge, gibt es eigentlich keine Beschränkungen für Dateinamen auf Unix-Systemen außer

  • Es kann nicht enthalten \ 0
  • Es kann nicht enthalten /

Alles andere ist faires Spiel.

$ 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

Ja, ich nur ANSI Farbcodes in einem Dateinamen gespeichert und ließ sie wirksam werden.

Für Unterhaltung, setzen Sie einen BEL Charakter in einem Verzeichnisnamen und beobachten Sie den Spaß, dass, wenn Sie CD hinein erfolgt;)

In einer Zeile:

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

Sie können auch setzen ‚_‘ Zeichen, um es besser lesbar zu machen (im Falle des Ersetzens slashs, zum Beispiel)

Sie könnten die re.sub () -Methode verwenden, etwas zu ersetzen, nicht „filelike“. Aber in der Tat, könnte jedes Zeichen gültig sein; so gibt es keine vorgefertigten Funktionen (glaube ich), um es getan.

import re

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

Würde in einem Dateihandle führen /tmp/filename.txt.

>>> 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'

Es behandelt nicht leere Strings, spezielle Dateinamen ( 'nul', 'con', etc).

Auch wenn Sie vorsichtig sein. Es ist nicht klar gesagt in Ihrem Intro, wenn Sie bei latine Sprache suchen. Einige Worte können bedeutungslos oder eine andere Bedeutung, wenn man sie mit ASCII-Zeichen sanieren nur.

stellen Sie sich "forêt poésie" (Wald Poesie) haben, Ihre sanitization geben könnte "fort-posie" (stark + etwas sinnlos)

Noch schlimmer, wenn Sie mit chinesischen Schriftzeichen zu tun haben.

„下 北 沢“ Ihr System könnte am Ende tun „---“, die nach einer Weile zum Scheitern verurteilt ist und nicht sehr hilfreich. Also, wenn Sie nur mit Dateien umgehen möchte ich ermutigen, entweder nennen sie eine generische Kette, die Sie die Zeichen zu halten steuern oder wie es ist. Für URIs, in etwa gleich.

Warum nicht einfach wickeln Sie das „osopen“ mit einem try / except und lassen Sie das zugrunde liegende Betriebssystem aussortieren, ob die Datei gültig ist?

Dies scheint viel weniger Arbeit und ist unabhängig davon gültig, welches Betriebssystem Sie verwenden.

Ein weiteres Problem, dass die anderen Kommentare noch nicht angesprochen haben, ist die leere Zeichenkette, die offensichtlich kein gültiger Dateiname ist. Sie können auch mit einem leeren String aus Strippen zu viele Zeichen am Ende.

Was mit dem Windows reservierten Dateinamen und Probleme mit Punkten, die sicherste Antwort auf die Frage „Wie kann ich normalisieren einen gültigen Dateinamen aus beliebigen Benutzereingabe?“ Ist „nicht einmal die Mühe versuchen“: wenn Sie irgendeine andere finden Art und Weise, es zu vermeiden (z. B. unter Verwendung von integer-Primärschlüssel aus einer Datenbank als Dateinamen), das zu tun.

Wenn Sie müssen, und Sie wirklich brauchen Räume zu ermöglichen und für Dateierweiterungen als Teil des Namens, versuchen Sie so etwas wie ‚‘:

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

Auch dies kann nicht richtig vor allem auf unerwarteten OSs garantiert werden - zum Beispiel RISC OS Räume hassen und verwendet als Verzeichnistrenner

‚‘.

Ich mochte den Python-slugify Ansatz hier, aber es war Strippen Punkte auch weg, was nicht erwünscht war. So optimiert ich es für einen sauberen Dateinamen das Hochladen auf diese Weise bis s3:

pip install python-slugify

Beispielcode:

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

Ausgabe:

>>> clean_filename
'very-unsafe-file-name-haha.txt'

Das ist so ausfallsicher, es funktioniert mit Dateinamen ohne Erweiterung und es funktioniert sogar für nur unsichere Zeichen Dateinamen (Ergebnis none hier).

Die meisten dieser Lösungen funktionieren nicht.

'/ hallo / world' -> 'Hello World'

'/ Hello World' / -> 'Hello World'

Das ist nicht das, was Sie in der Regel wollen, sagen Sie den HTML-Code für jeden Link speichern, wirst du die HTML für eine andere Webseite zu überschreiben.

Ich Beize ein dict wie:

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

2 steht für die Zahl, die auf die nächsten Dateinamen angehängt werden sollen.

Ich blicke auf den Dateinamen jedes Mal aus dem dict. Wenn er nicht da ist, erstelle ich einen neuen, die maximale Anzahl anhängt, wenn nötig.

Nicht genau das, was OP fragt nach, aber das ist, was ich benutze, weil ich einzigartig und reversible Umwandlungen muß:

# 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+-_ .'))

Das Ergebnis ist „etwas“ lesbar, zumindest aus einer Sysadmin Sicht.

Ich bin sicher, dies ist keine große Antwort, da es die Zeichenfolge ändert es Schleifen über, aber es scheint in Ordnung zu arbeiten:

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, '')

UPDATE

Alle irreparabel gebrochen Links in dieser 6-jährigen Antwort.

Auch ich würde es auch auf diese Weise nicht mehr tun, nur base64 kodieren oder unsichere Zeichen fallen. Python 3 Beispiel:

import re
t = re.compile("[a-zA-Z0-9.,_-]")
unsafe = "abc∂éåß®∆˚˙©¬ñ√ƒµ©∆∫ø"
safe = [ch for ch in unsafe if t.match(ch)]
# => 'abc'

Mit base64 können Sie kodieren und zu dekodieren, so dass Sie wieder die ursprünglichen Dateinamen abgerufen werden.

Aber je nach Anwendungsfall können Sie einen zufälligen Dateinamen besser dran zu erzeugen und die Metadaten in separater Datei oder DB zu speichern.

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'

ORIGINAL LINKROTTEN ANTWORT :

Das bobcat Projekt enthält ein Python-Modul, das genau dies tut.

Es ist nicht ganz robust, sehen diese Post und diese antworten .

So

, wie erwähnt. base64 Codierung ist wahrscheinlich eine bessere Idee, wenn die Lesbarkeit keine Rolle spielt

Ich weiß, es gibt viele Antworten, aber sie meist auf regulären Ausdrücken oder externe Module verlassen, so mag ich in meiner eigenen Antwort zu werfen. Eine reine Python-Funktion, kein externes Modul benötigt, kein regulärer Ausdruck verwendet. Mein Ansatz ist nicht ungültig Zeichen zu reinigen, sondern nur gültig diejenigen zu ermöglichen.

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    

Wenn Sie möchten, können Sie Ihre eigenen gültigen Zeichen an dem validchars Variable am Anfang, wie Sie Ihre nationalen Buchstaben hinzufügen, die in englischem Alphabet nicht existieren. Dies ist etwas, was Sie können oder nicht wollen. Einige Dateisysteme, die auf UTF-8 nicht laufen nach wie vor Probleme mit Nicht-ASCII-Zeichen haben könnte

Diese Funktion ist für eine einzelne Dateinamen Gültigkeit zu prüfen, so wird es Pfadtrenn mit _ Berücksichtigung ihnen ungültige Zeichen ersetzen. Falls Sie Fragen hinzufügen möchten, ist es trivial, die if zu ändern, um os Pfadseparator zu umfassen.

Antwort für Python modifizierte 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)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top