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.

2voto

makeroo Punkte 471

Nicht genau das, wonach der OP gefragt hat, aber das ist, was ich verwende, weil ich eindeutige und umkehrbare Konvertierungen benötige:

# 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 "einigermaßen" lesbar, zumindest aus der Sicht eines Systemadministrators.

0 Stimmen

Ein Wrapper dafür ohne Leerzeichen in Dateinamen: def safe_filename(filename): return safePath(filename.strip().replace(' ','_'))

0 Stimmen

1) Erlaubt keine Unicode-Zeichen, die tatsächlich gültige Dateinamenszeichen sind. 2) Können Sie eine inverse Funktion bereitstellen? Liebe eine mathematisch orientierte Lösung :)

2voto

Alex Punkte 86

Bei demselben Problem habe ich python-slugify verwendet.

Die Verwendung wurde auch von Shoham vorgeschlagen, aber, wie therealmarv darauf hingewiesen hat, konvertiert python-slugify standardmäßig auch Punkte.

Dieses Verhalten kann überstimmt werden, indem Punkte in das regex_pattern-Argument eingeschlossen werden.

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

Beachten Sie, dass das Regex-Muster aus der

ALLOWED_CHARS_PATTERN_WITH_UPPERCASE

globalen Variable innerhalb der Datei slugify.py des python-slugify-Pakets kopiert wurde und um "." erweitert wurde.

Denken Sie daran, dass Sonderzeichen wie .() mit \ escapet werden müssen.

Wenn Sie Großbuchstaben beibehalten möchten, verwenden Sie das Argument 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'

Dies hat mit Python 3.8.4 und python-slugify 4.0.1 funktioniert

1voto

robert king Punkte 15261

Die meisten dieser Lösungen funktionieren nicht.

'/hallo/welt' -> 'helloworld'

'/helloworld'/ -> 'helloworld'

Im Allgemeinen ist das nicht das, was du möchtest. Angenommen, du speicherst den HTML-Code für jeden Link, dann überschreibst du den HTML-Code für eine andere Webseite.

Ich speichere ein Dictionary wie folgt:

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

Die 2 repräsentiert die Zahl, die dem nächsten Dateinamen angehängt werden soll.

Ich suche jedes Mal den Dateinamen im Dictionary nach. Wenn er nicht vorhanden ist, erstelle ich einen neuen, wobei bei Bedarf die maximale Zahl angehängt wird.

0 Stimmen

Bitte beachten Sie, dass Sie, wenn Sie "helloworld1" verwenden, auch überprüfen müssen, ob "helloworld1" verwendet wird usw.

0voto

TankorSmash Punkte 11579

Ich bin mir sicher, dass dies keine großartige Antwort ist, da es den String ändert, über den es iteriert, aber es scheint ganz gut zu funktionieren:

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

0 Stimmen

Ich habe diesen "".join( x for x in s if (x.isalnum() or x in "._- ")) in den Kommentaren zu diesem Beitrag gefunden.

0voto

ChaimG Punkte 5736

Hier sollten alle möglichen Probleme abgedeckt sein. Es behandelt alle Arten von Problemen für Sie, einschließlich (aber nicht beschränkt auf) Zeichenersetzungen.

Funktioniert auf Windows, *nix und fast jedem anderen Dateisystem. Erlaubt nur druckbare Zeichen.

def txt2filename(txt, chr_set='normal'):
    """Konvertiert txt in einen gültigen Windows/*nix Dateinamen nur mit druckbaren Zeichen.

    args:
        txt: Der zu konvertierende String.
        chr_set: 'normal', 'universal' oder 'inclusive'.
            'universal':    ' -.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
            'normal':       Jedes druckbare Zeichen außer denen, die auf Windows/*nix nicht erlaubt sind.
            'extended':     Alle 'normalen' Zeichen plus die erweiterten Zeichen ASCII-Codes 128-255
    """

    FILLER = '-'

    # Schritt 1: Ausgeschlossene Zeichen entfernen.
    if chr_set == 'universal':
        # Suchen in einem Satz ist O(n) gegenüber O(n * x) für einen String.
        printables = set(' -.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
    else:
        if chr_set == 'normal':
            max_chr = 127
        elif chr_set == 'extended':
            max_chr = 256
        else:
            raise ValueError(f'Das chr_set-Argument kann normal, erweitert oder universal sein; nicht {chr_set=}')
        EXCLUDED_CHRS = set(r'<>:"/\|?*')               # Verbotene Zeichen in Windows-Dateinamen.
        EXCLUDED_CHRS.update(chr(127))                  # DEL (nicht druckbar).
        printables = set(chr(x)
                         for x in range(32, max_chr)
                         if chr(x) not in EXCLUDED_CHRS)
    result = ''.join(x if x in printables else FILLER   # Nur druckbare Zeichen erlauben.
                     for x in txt)

    # Schritt 2: Gerätenamen, '.' und '..' sind ungültige Dateinamen in Windows.
    DEVICE_NAMES = 'CON,PRN,AUX,NUL,COM1,COM2,COM3,COM4,' \
                   'COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,' \
                   'LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,' \
                   'CONIN$,CONOUT$,..,.'.split()        # Diese Liste ist eine O(n)-Operation.
    if result in DEVICE_NAMES:
        result = f'-{result}-'

    # Schritt 3: Maximale Dateinamenlänge beträgt 255 Byte in Windows und Linux (andere *nix-Varianten können längere Namen zulassen).
    result = result[:255]

    # Schritt 4: Windows erlaubt keine Dateinamen, die mit '.' oder ' ' enden oder mit ' ' beginnen.
    result = re.sub(r'^[. ]', FILLER, result)
    result = re.sub(r' $', FILLER, result)

    return result

Diese Lösung benötigt keine externen Bibliotheken. Sie substituiert auch nicht druckbare Dateinamen, weil sie nicht immer einfach zu handhaben sind.

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