2008-10-08 20 views
14

Todavía estoy 'inmerso' en Python, y quiero asegurarme de que no estoy pasando por alto algo. Escribí un script que extraía archivos de varios archivos zip y guardaba los archivos extraídos en un directorio. Para evitar que los nombres de archivos duplicados se sobrescriban, escribí esta pequeña función, ¿y me pregunto si existe una forma mejor de hacerlo? Gracias!¿Es esta la mejor manera de obtener una versión única del nombre de archivo w/Python?

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 

Realmente hago requieren los archivos de estar en un solo directorio, y la numeración de los duplicados es definitivamente aceptable en mi caso, así que no estoy buscando un método más robusto (aunque' supongo que cualquier punteros son bienvenidos), pero solo para asegurarse de que lo que esto logra se haga de la manera correcta.

Respuesta

22

Un problema es que hay una condición de carrera en el código anterior, ya que hay una brecha entre la prueba de existencia y la creación del archivo. Puede haber implicaciones de seguridad para esto (piense en alguien que inserta maliciosamente un enlace simbólico a un archivo confidencial que no podría sobrescribir, pero su programa que se ejecuta con un privilegio mayor podría) Ataques como estos son el por qué cosas como os.tempnam () están en desuso.

Para evitarlo, el mejor enfoque es tratar de crear el archivo de tal manera que obtendrá una excepción si falla y, en caso de éxito, devolverá el objeto del archivo realmente abierto. Esto se puede hacer con las funciones os.open de nivel inferior, pasando las banderas os.O_CREAT y os.O_EXCL. Una vez abierto, devuelve el archivo real (y, opcionalmente, el nombre de archivo) que crees. Por ejemplo, aquí está el código modificado para utilizar este enfoque (volviendo una presentación (archivo, nombre de archivo) tupla):

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 

[Editar] En realidad, una mejor manera, que se encargará de las cuestiones antes mencionadas para usted, es probablemente para usar el módulo de tempfile, aunque puede perder cierto control sobre la nomenclatura.He aquí un ejemplo de su uso (manteniendo una interfaz similar):

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 

El único inconveniente de este enfoque es que siempre se obtendrá un nombre de archivo con algunos caracteres aleatorios en el mismo, ya que no hay intento de crear un archivo sin modificar (/home/some_dir/foo.txt) primero. Es posible que también desee examinar tempfile.TemporaryFile y NamedTemporaryFile, que hará lo anterior y también eliminará automáticamente del disco cuando se cierre.

+0

Sí, este es The_Right_Way para hacerlo. ¡Ojalá pudiera moderarme y poner tu respuesta en la cima! –

+1

Pequeño error tipográfico: debería ser 'os.O_RDWR' en lugar de' os.O_RDRW' – tremby

1

Si desea nombres legibles, esta parece una buena solución.
Existen rutinas para devolver nombres de archivos únicos, por ejemplo. archivos temporales, pero producen nombres largos de apariencia aleatoria.

2

Dos pequeños cambios ...

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

se obtienen dos resultados con significado distinto, les dan nombres distintos.

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

No es más rápido o significativamente más corto. Pero, cuando quiera cambiar el patrón de su nombre de archivo, el patrón está en un solo lugar y es un poco más fácil trabajar con él.

6

Sí, esta es una buena estrategia para nombres de archivo legibles pero únicos.

Un cambio importante: Debe reemplazar os.path.isfile con os.path.lexists! Como está escrito ahora, si hay un directorio llamado /foo/bar.baz, su programa intentará sobrescribirlo con el nuevo archivo (que no funcionará) ... ya que isfile solo busca archivos y no directorios . lexists busca directorios, enlaces simbólicos, etc. ... básicamente si hay alguna razón por la que no se pueda crear el nombre del archivo.

EDITAR: @Brian dio una mejor respuesta, que es más segura y robusta en términos de condiciones de carrera.

1

si no te importa la legibilidad, uuid.uuid4() es tu amigo.

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

¿Qué tal

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 

tiempo() devuelve la hora actual en milisegundos. combinado con el nombre de archivo original, es bastante único, incluso en casos complejos con múltiples subprocesos.

Cuestiones relacionadas