2008-10-08 5 views
14

Immer noch tauchen Sie in Python ein und wollen sichergehen, dass ich nichts übersehen habe. Ich habe ein Skript geschrieben, das Dateien aus mehreren ZIP-Dateien extrahiert und die extrahierten Dateien in einem Verzeichnis speichert. Um zu verhindern, dass doppelte Dateinamen überschrieben werden, habe ich diese kleine Funktion geschrieben - und ich frage mich, ob es einen besseren Weg dafür gibt? Danke!Ist dies der beste Weg, um eine einzigartige Version des Dateinamens mit Python zu erhalten?

def unique_filename(file_name): 
counter = 1 
file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext') 
while os.path.isfile(file_name): 
    file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1] 
    counter += 1 
return file_name 

ich wirklich tun benötigen, um die Dateien in einem einzigen Verzeichnis zu sein, und Duplikate Nummerierung ist auf jeden Fall akzeptabel in meinem Fall, so dass ich nicht für ein robusteres Verfahren suchen (tho‘ich irgendwelche Hinweise annehmen sind willkommen), aber nur um sicherzustellen, dass das, was dies bewirkt, richtig gemacht wird.

Antwort

22

Ein Problem ist, dass es in Ihrem obigen Code eine Race Condition gibt, da es eine Lücke zwischen dem Testen auf Existenz und dem Erstellen der Datei gibt. Es kann Sicherheitsbedenken geben (denken Sie daran, dass jemand einen Symlink böswillig in eine sensible Datei einfügt, die er nicht überschreiben könnte, aber Ihr Programm könnte mit einem höheren Privileg ausgeführt werden). Angriffe wie diese sind Gründe, warum os.tempnam () sind veraltet.

Um es zu umgehen, ist der beste Ansatz, die Datei tatsächlich so zu erstellen, dass Sie eine Ausnahme erhalten, wenn sie fehlschlägt, und bei Erfolg das tatsächlich geöffnete Dateiobjekt zurückgibt. Dies kann mit den os.open-Funktionen auf der unteren Ebene erfolgen, indem die beiden Flags os.O_CREAT und os.O_EXCL übergeben werden. Nach dem Öffnen geben Sie die tatsächliche Datei (und optional den Dateinamen) zurück, die Sie erstellt haben. Zum Beispiel, hier ist der Code geändert, diesen Ansatz zu verwenden (eine (Datei, Dateiname) Tupel Rückkehr):

def unique_file(file_name): 
    counter = 1 
    file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext') 
    while 1: 
     try: 
      fd = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_RDRW) 
      return os.fdopen(fd), file_name 
     except OSError: 
      pass 
     file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1] 
     counter += 1 

[Bearbeiten] Eigentlich eine bessere Art und Weise, die die oben genannten Fragen für Sie, wahrscheinlich handhaben ist um das Tempfilemodul zu verwenden, obwohl Sie einige Kontrolle über die Benennung verlieren können.Hier ist ein Beispiel, es zu benutzen (eine ähnliche Schnittstelle zu halten):

def unique_file(file_name): 
    dirname, filename = os.path.split(file_name) 
    prefix, suffix = os.path.splitext(filename) 

    fd, filename = tempfile.mkstemp(suffix, prefix+"_", dirname) 
    return os.fdopen(fd), filename 

>>> f, filename=unique_file('/home/some_dir/foo.txt') 
>>> print filename 
/home/some_dir/foo_z8f_2Z.txt 

Der einzige Nachteil bei diesem Ansatz ist, dass Sie immer einen Dateinamen mit einigen zufälligen Zeichen in ihm bekommen, da es kein Versuch ist eine unveränderte Datei zu erstellen (/home/some_dir/foo.txt) zuerst. Sie können auch tempfile.TemporaryFile und NamedTemporaryFile sehen, die das obige tun und auch automatisch von der Festplatte löschen, wenn sie geschlossen werden.

+0

Ja, das ist The_Right_Way, es zu tun. Ich wünschte, ich könnte mich selbst abwandeln und deine Antwort an die Spitze setzen! –

+1

Kleiner Tippfehler: das sollte "os.O_RDWR" statt "os.O_RDRW" sein – tremby

1

Wenn Sie lesbare Namen möchten, sieht das nach einer guten Lösung aus.
Es gibt Routinen zum Zurückgeben eindeutiger Dateinamen für z. Temp-Dateien, aber sie produzieren lange zufällig aussehende Namen.

2

Zwei kleine Änderungen ...

base_name, ext = os.path.splitext(file_name) 

Sie erhalten zwei Ergebnisse mit unterschiedlicher Bedeutung, geben ihnen verschiedene Namen.

file_name = "%s_%d%s" % (base_name, str(counter), ext) 

Es ist nicht schneller oder wesentlich kürzer. Wenn Sie jedoch das Dateinamensmuster ändern möchten, befindet sich das Muster an einer Stelle und es ist leichter, mit ihm zu arbeiten.

6

Ja, das ist eine gute Strategie für lesbare, aber eindeutige Dateinamen.

Eine wichtige Änderung: Sie sollten os.path.isfile durch os.path.lexists ersetzen! Wenn es ein Verzeichnis namens /foo/bar.baz gibt, versucht Ihr Programm dies mit der neuen Datei zu überschreiben (was nicht funktioniert) ... da isfile nur nach Dateien und nicht nach Verzeichnissen sucht . lexists prüft auf Verzeichnisse, Symlinks, etc ... im Grunde, wenn es einen Grund gibt, dass Dateiname nicht erstellt werden konnte.

EDIT: @Brian gab eine bessere Antwort, die in Bezug auf Race-Bedingungen sicherer und robuster ist.

1

Wenn Ihnen die Lesbarkeit egal ist, ist uuid.uuid4() Ihr Freund.

import uuid 

def unique_filename(prefix=None, suffix=None): 
    fn = [] 
    if prefix: fn.extend([prefix, '-']) 
    fn.append(str(uuid.uuid4())) 
    if suffix: fn.extend(['.', suffix.lstrip('.')]) 
    return ''.join(fn) 
0

Wie wäre es

def ensure_unique_filename(orig_file_path):  
    from time import time 
    import os 

    if os.path.lexists(orig_file_path): 
     name, ext = os.path.splitext(orig_file_path) 
     orig_file_path = name + str(time()).replace('.', '') + ext 

    return orig_file_path 

time() in Millisekunden aktuelle Zeit zurückgibt. Kombiniert mit dem ursprünglichen Dateinamen ist es sogar in komplexen Multithread-Fällen ziemlich einzigartig.

Verwandte Themen