454 Stimmen

Umwandeln Sie eine Zeichenfolge in einen gültigen Dateinamen?

Ich habe einen String, den ich als Dateinamen verwenden möchte, daher möchte ich alle Zeichen entfernen, die in Dateinamen nicht erlaubt wären, mit Python.

Ich wäre lieber streng als nachlässig, also möchte ich nur Buchstaben, Zahlen und eine kleine Menge anderer Zeichen wie "_-.() " behalten. Was ist die eleganteste Lösung?

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

35 Stimmen

Sollte dies nicht in das os.path-Modul integriert sein?

5 Stimmen

Vielleicht, obwohl ihr Anwendungsfall einen einzigen sicheren Pfad über alle Plattformen erfordern würde, nicht nur die aktuelle, wofür das Betriebssystem nicht konzipiert ist, um damit umzugehen.

6 Stimmen

Um den obigen Kommentar zu erweitern: Das aktuelle Design von os.path lädt tatsächlich je nach Betriebssystem eine andere Bibliothek (siehe die zweite Notiz in der Dokumentation). Wenn eine Quoting-Funktion in os.path implementiert wäre, könnte sie nur das Zeichenfolge für POSIX-Sicherheit quoten, wenn sie auf einem POSIX-System ausgeführt wird, oder für Windows-Sicherheit, wenn sie auf Windows ausgeführt wird. Der resultierende Dateiname wäre nicht unbedingt gültig für sowohl Windows als auch POSIX, was die Frage verlangt.

6voto

bobince Punkte 512550

Ein weiteres Problem, das in den anderen Kommentaren noch nicht angesprochen wurde, ist der leere String, der offensichtlich kein gültiger Dateiname ist. Sie können auch einen leeren String erhalten, wenn Sie zu viele Zeichen entfernen.

Was die Windows-reservierten Dateinamen und Probleme mit Punkten betrifft, ist die sicherste Antwort auf die Frage "Wie normalisiere ich einen gültigen Dateinamen aus beliebiger Benutzereingabe?" "Versuchen Sie es erst gar nicht": Wenn Sie einen anderen Weg finden können, um dies zu vermeiden (z. B. die Verwendung von Ganzzahl-Primärschlüsseln aus einer Datenbank als Dateinamen), tun Sie das.

Wenn Sie müssen und wirklich Leerzeichen und '.' für Dateierweiterungen als Teil des Namens zulassen müssen, versuchen Sie etwas Ähnliches 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

Selbst dies kann nicht garantiert richtig sein, insbesondere auf unerwarteten Betriebssystemen — zum Beispiel hasst RISC OS Leerzeichen und verwendet '.' als Verzeichnistrennzeichen.

6voto

James Anderson Punkte 26827

Warum nicht einfach das "osopen" in einen try/except-Block einpacken und das zugrunde liegende Betriebssystem entscheiden lassen, ob die Datei gültig ist?

Dies scheint viel weniger Arbeit zu sein und ist unabhängig davon, welches Betriebssystem Sie verwenden.

6 Stimmen

Überprüft es jedoch den Namen? Ich meine, wenn das Betriebssystem nicht zufrieden ist, müssen Sie trotzdem etwas tun, oder?

1 Stimmen

In einigen Fällen kann das Betriebssystem/Sprache Ihren Dateinamen stillschweigend in eine alternative Form verändern, aber wenn Sie eine Verzeichnisliste erstellen, erhalten Sie einen anderen Namen. Und das kann zu einem Problem führen, bei dem "wenn ich die Datei schreibe, ist sie da, aber wenn ich nach der Datei suche, heißt sie anders". (Ich spreche über ein Verhalten, von dem ich auf VAX gehört habe ...)

0 Stimmen

Darüber hinaus muss der Dateiname auf mehreren Betriebssystemen gültig sein, was Sie mit einem auf einer Maschine ausgeführten osopen nicht erkennen können.

6voto

jfs Punkte 370717
>>> 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 keine leeren Strings, spezielle Dateinamen ('nul', 'con', etc.).

0 Stimmen

+1 für Übersetzungstabellen, es ist mit Abstand die effizienteste Methode. Für die speziellen Dateinamen/Leerstellen genügt eine einfache Vorbedingungsprüfung und für überflüssige Punkte ist das ebenfalls eine einfache Korrektur.

1 Stimmen

Während Übersetzen etwas effizienter ist als ein Regulärer Ausdruck, wird diese Zeit höchstwahrscheinlich in den Schatten gestellt, wenn Sie tatsächlich versuchen, die Datei zu öffnen, was Sie zweifellos vorhaben. Daher bevorzuge ich eine lesbarere Reguläre-Ausdruck-Lösung als das Durcheinander oben

0 Stimmen

Ich mache mir auch Sorgen wegen der Blacklist. Zugegeben, es handelt sich um eine Blacklist, die auf einer Whitelist basiert, aber trotzdem. Es scheint weniger... sicher zu sein. Wie weißt du, dass "allchars" tatsächlich vollständig ist?

5voto

Antwort modifiziert für 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)

1 Stimmen

Könnten Sie Ihre Antwort im Detail erklären?

1 Stimmen

Es ist die gleiche Antwort, die von Sophie Gage akzeptiert wurde. Aber sie wurde geändert, um mit Python 3.6 zu funktionieren.

3voto

RexBarker Punkte 958

Yet another answer for Windows specific paths, using simple replacement and no funky modules:

import re

def check_for_illegal_char(input_str):
    # entferne ungültige Zeichen für Windows-Dateinamen/-Pfade
    # (ungültige Dateinamen sind eine Obermenge (41) der ungültigen Pfadnamen (36))
    # gemäß der Windows-Blacklist, die mit Powershell abgerufen wurde
    # von: https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names/44750843#44750843
    #
    # PS> $enc = [system.Text.Encoding]::UTF8
    # PS> $FileNameInvalidChars = [System.IO.Path]::GetInvalidFileNameChars()
    # PS> $FileNameInvalidChars | foreach { $enc.GetBytes($_) } | Out-File -FilePath InvalidFileCharCodes.txt

    illegal = '\u0022\u003c\u003e\u007c\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008' + \
              '\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015' + \
              '\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f\u003a\u002a\u003f\u005c\u002f' 

    output_str, _ = re.subn('['+illegal+']','_', input_str)
    output_str = output_str.replace('\\','_')   # backslash kann nicht von regex behandelt werden
    output_str = output_str.replace('..','_')   # doppelte Punkte sind auch ungültig, oder zumindest eine schlechte Idee
    output_str = output_str[:-1] if output_str[-1] == '.' else output_str # darf kein Ende der Zeile '.' haben

    if output_str != input_str:
        print(f"Der Name '{input_str}' enthielt ungültige Zeichen, "
              f"Name wurde zu '{output_str}' geändert")

    return output_str

Wenn getestet mit check_for_illegal_char('fas\u0003\u0004good\\..asd.'), erhalte ich:

Der Name 'fasgood\..asd.' enthielt ungültige Zeichen, Name wurde zu 'fas__good__asd' geändert

2 Stimmen

Das hat bei mir super funktioniert, ich habe nur den Ersatz des Backslashes auskommentiert, da ich dies mit os.makedirs verwendet habe (ich brauchte es, um Verzeichnisse zu erstellen...) Aber tolle Antwort, danke.

0 Stimmen

Aus irgendeinem unbekannten Grund musste ich als Python-Neuling das '\u0022' durch '\x22' usw. ersetzen, um es zum Laufen zu bringen. Danke.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X