Pergunta sobre python – Crie com segurança um arquivo se e somente se ele não existir com python

82

Eu gostaria de escrever para um arquivo baseado em se esse arquivo já existe ou não, apenas escrevendo se ele ainda não existe (na prática, eu quero continuar tentando arquivos até encontrar um que não existe).

O código a seguir mostra uma maneira em que um invasor potencialmente poderia inserir um symlink, como sugerido emesta postagem entre um teste para o arquivo e o arquivo sendo gravado. Se o código for executado com permissões suficientemente altas, isso poderá substituir um arquivo arbitrário.

Existe alguma maneira de resolver este problema?

import os
import errno

file_to_be_attacked = 'important_file'

with open(file_to_be_attacked, 'w') as f:
    f.write('Some important content!\n')

test_file = 'testfile'

try:
    with open(test_file) as f: pass
except IOError, e:

    # symlink created here
    os.symlink(file_to_be_attacked, test_file)

    if e.errno != errno.ENOENT:
        raise
    else:
        with open(test_file, 'w') as f:
            f.write('Hello, kthxbye!\n')
Verifique a escrita atômica com Pythonstackoverflow.com/questions/2333872/… Mikko Ohtamaa
Você poderia escrever o arquivo em um local temporário e, em seguida, fazer um comando de cópia sem permitir a sobrescrita? Eric
Ah ok. Eu entendi qual é o problema ... você escreve SOMENTE se o arquivo existir? Mikko Ohtamaa
@ Mikko Isso não ajuda aqui. Konrad Rudolph

Sua resposta

2   a resposta
52

Para referência, o Python 3.3 implementa um novo'x' modo noopen() função para cobrir este caso de uso (criar somente, falhar se o arquivo existir). Note que o'x' o modo é especificado sozinho. Usando'wx' resulta em umValueError Enquanto o'w' é redundante (a única coisa que você pode fazer se a chamada tiver sucesso é gravar no arquivo de qualquer maneira; não pode ter existido se a chamada tiver êxito):

>>> f1 = open('new_binary_file', 'xb')
>>> f2 = open('new_text_file', 'x')

Para o Python 3.2 e abaixo (incluindo o Python 2.x), consultea resposta aceita.

Python 3.6:ValueError: must have exactly one of create/read/write/append mode Szabolcs Dombi
Farei isso - embora tenha que esperar até que eu volte na frente de um computador um pouco mais tarde. Dave Jones
sim você está certo. você pode estender a resposta com os 2 exemplos? Szabolcs Dombi
Veja o comentário acima :-) Dave Jones
81

Editar: Veja tambémResposta de Dave Jones: do Python 3.3, você pode usar ox bandeira paraopen() para fornecer essa função.

Resposta original abaixo

Sim, mas não usando o padrão do Pythonopen() ligar. Você precisará usaros.open() em vez disso, permite especificar sinalizadores para o código C subjacente.

Em particular, você quer usarO_CREAT | O_EXCL. Na página man deopen(2) debaixoO_EXCL no meu sistema Unix:

Certifique-se de que esta chamada crie o arquivo: se este sinalizador for especificado em conjunto comO_CREAT, e o nome do caminho já existeopen() vai falhar. O comportamento deO_EXCL é indefinido seO_CREAT não é especificado.

Quando estes dois flags são especificados, os links simbólicos não são seguidos: se o nome do caminho é um link simbólico, entãoopen() falha independentemente de onde o link simbólico aponta.

O_EXCL é suportado apenas no NFS ao usar o NFSv3 ou posterior no kernel 2.6 ou posterior. Em ambientes onde o NFSO_EXCL suporte não é fornecido, programas que dependem dele para executar tarefas de bloqueio conterão uma condição de corrida.

Então não é perfeito, mas o AFAIK é o mais próximo que você pode conseguir para evitar essa condição de corrida.

Edit: as outras regras de usoos.open() ao invés deopen() ainda se aplicam. Em particular, se você quiser usar o descritor de arquivo retornado para leitura ou escrita, você precisará de um dosO_RDONLY, O_WRONLY ouO_RDWR bandeiras também.

TodosO_* bandeiras estão em Pythonos módulo, então você precisaimport os E useos.O_CREAT etc.

Exemplo:
import os
import errno

flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY

try:
    file_handle = os.open('filename', flags)
except OSError as e:
    if e.errno == errno.EEXIST:  # Failed as the file already exists.
        pass
    else:  # Something unexpected went wrong so reraise the exception.
        raise
else:  # No exception, so the file must have been created successfully.
    with os.fdopen(file_handle, 'w') as file_obj:
        # Using `os.fdopen` converts the handle to an object that acts like a
        # regular Python file object, and the `with` context manager means the
        # file will be automatically closed when we're done with it.
        file_obj.write("Look, ma, I'm writing to a new file!")
@me_e você poderia adicionar um exemplo por favor? 030
@me_e você também pode fazer isso: f = open ('file.txt', 'a') f.close () f = open ('file.txt', 'r +'), então python cria um arquivo somente se não existe, fecha e abre o arquivo [antigo ou novo] ...? pinhead
@pinhead Bem vindo ao Stack Overflow! É melhor fazer perguntas em novas perguntas, porque elas são mais visíveis e você não está limitado pela formatação de comentários ou pelos limites de caracteres. Em qualquer caso, o seu código não resolve o problema, onde explicitamente queremos abrir o arquivo see somente se isso ainda não existe. me_and
Eu ficaria mais preocupado com a versão do kernel, pessoalmente. Se você está rodando algo parecido com um sistema atualizado, você não deve ter problemas, mas o RHEL 3 (ainda em fase de suporte estendido) está rodando um kernel 2.4, por exemplo. Além disso, eu não investiguei se eles fornecem gravações atômicas no Windows em FAT ou NTFS, o que é uma limitação potencialmente grande. me_and

Perguntas relacionadas