É esta a melhor maneira de obter a versão única de nome de arquivo w / Python?

StackOverflow https://stackoverflow.com/questions/183480

  •  06-07-2019
  •  | 
  •  

Pergunta

Ainda 'mergulho em' para Python, e quero ter certeza que eu não estou esquecendo algo. Eu escrevi um script que extrai arquivos de vários arquivos zip, e salva os arquivos extraídos juntos em um diretório. Para evitar nomes de arquivos duplicados de ser sobre-escrito, eu escrevi essa pequena função - e eu só estou querendo saber se existe uma maneira melhor de fazer isso? Obrigado!

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

Eu realmente do requerem os arquivos para estar em um único diretório, e numeração de duplicatas é definitivamente aceitável no meu caso, então eu não estou procurando um método mais robusto (tho' Suponho que qualquer ponteiros são bem-vindos), mas apenas para se certificar de que o que isso realiza está sendo feito da maneira certa.

Foi útil?

Solução

Uma questão é que existe uma condição de corrida em seu código acima, uma vez que existe uma lacuna entre o teste de existência, e criar o arquivo. Pode haver implicações de segurança para este (Pense em alguém maliciosamente inserir um link simbólico para um arquivo confidencial que não seria capaz de substituir, mas o seu programa em execução com um privilégio mais alto possível) Ataques como estes são porque coisas como os.tempnam ( ) estão obsoletos.

Para contornar o problema, a melhor abordagem é realmente tentar criar o arquivo de tal forma que você vai ter uma exceção se ele falhar, e em caso de sucesso, retornar o objeto de arquivo, na verdade, aberto. Isso pode ser feito com o nível mais baixo os.open funções, passando as bandeiras os.O_CREAT e os.O_EXCL. Depois de aberto, o retorno do arquivo real (e, opcionalmente, filename) você cria. Por exemplo, aqui está o código modificado para usar essa abordagem (retornando um (arquivo, filename) tuple):

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

[Edit] Na verdade, uma maneira melhor, que vai lidar com as questões acima para você, é, provavelmente, usar o módulo tempfile, embora você pode perder algum controle sobre a nomeação. Aqui está um exemplo de usá-lo (mantendo uma interface 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

A única desvantagem dessa abordagem é que você sempre vai ter um nome de arquivo com alguns caracteres aleatórios na mesma, como não há nenhuma tentativa de criar um arquivo modificado (/home/some_dir/foo.txt) em primeiro lugar. Você também pode querer olhar para tempfile.TemporaryFile e NamedTemporaryFile, que vai fazer o exposto, e também automaticamente excluir do disco quando fechado.

Outras dicas

Sim, esta é uma boa estratégia para nomes de arquivos legíveis, mas únicos.

Uma mudança importante : Você deve substituir os.path.isfile com os.path.lexists! Como está escrito no momento, se há um diretório chamado /foo/bar.baz, o programa irá tentar substituir que com o novo arquivo (que não vai funcionar) ... desde que isfile somente verifica arquivos e não diretórios . cheques lexists para diretórios, links simbólicos, etc ... basicamente, se há alguma razão que nome de arquivo não pôde ser criado.

EDIT:. @ Brian deu uma resposta melhor, o que é mais seguro e robusto em termos de condições de corrida

Duas pequenas mudanças ...

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

Você tem dois resultados com significado distinto, dar-lhes nomes distintos.

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

Não é mais rápido ou significativamente menor. Mas, quando você quiser mudar o seu padrão de nome de arquivo, o padrão está em um lugar, e um pouco mais fácil de trabalhar.

Se você quiser nomes legíveis esta parece ser uma boa solução.
Existem rotinas para retornar nomes de arquivo exclusivo para, por exemplo. arquivos temporários, mas eles produzem longos nomes aleatórios olhando.

Se você não se preocupam com a legibilidade, uuid.uuid4 () é seu 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)

Como cerca

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 () retorna o tempo atual em milissegundos. combinado com nome de arquivo original, que é bastante singular, mesmo em casos de vários segmentos complexos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top