Como modificar um arquivo de texto?

175

Estou usando Python e gostaria de inserir uma string em um arquivo de texto sem excluir ou copiar o arquivo. Como eu posso fazer isso?

Martineau
fonte
1
Você pode consultar esta resposta por Alex Martelli.
Alok
@Ani o outro post é uma duplicata de Inserir linha na posição especificada de um arquivo de texto de qualquer maneira e certamente existem respostas compostas claras aqui: por que não adicionar sua resposta aqui em vez da outra maneira? A resposta aceita não é um requisito para uma boa pergunta.
Bhargav Rao
@BhargavRao Vote retraído. Eu deveria ter encontrado essa duplicata!
Ani Menon

Respostas:

134

Infelizmente, não há como inserir no meio de um arquivo sem reescrevê-lo. Como os cartazes anteriores indicaram, você pode anexar um arquivo ou substituir parte dele usando o comando see, mas se desejar adicionar itens no início ou no meio, precisará reescrevê-lo.

Isso é coisa do sistema operacional, não do Python. É o mesmo em todas as línguas.

O que costumo fazer é ler o arquivo, fazer as modificações e gravá-lo em um novo arquivo chamado myfile.txt.tmp ou algo parecido. Isso é melhor do que ler o arquivo inteiro na memória, porque o arquivo pode ser muito grande para isso. Depois que o arquivo temporário é concluído, renomeio o mesmo para o arquivo original.

Essa é uma maneira boa e segura de fazer isso, porque, se a gravação do arquivo travar ou for interrompida por qualquer motivo, você ainda terá o arquivo original intocado.

Adam Pierce
fonte
3
Ferramentas unix como o awk / sed fazem algo semelhante em seu código?
precisa
Não é verdade que isso seja o mesmo em todos os idiomas. No ActionScript: fileStream.openAsync (nome do arquivo, FileMode.UPDATE); Então eu posso ir a qualquer lugar do arquivo que desejar e alterar qualquer coisa.
AndrewBenjamin
2
@AndrewBenjamin Você sabe quais chamadas do sistema o ActionScript está fazendo? Existe a possibilidade do openAsync ler o arquivo e gravar um novo após a chamada?
AlexLordThorsen
@Rawrgulmuffins Eu não. No entanto, eu sei que ele não está lendo o arquivo inteiro na memória, pois eu o usei para lidar com tamanhos de arquivo de vários GB. Eu suspeito que é o mesmo que escrever com c # streamwriter. Eu vejo o python como uma ferramenta para fazer pequenas coisas rapidamente, em vez de desenvolvimento em larga escala e manipulação de arquivos.
AndrewBenjamin
4
@AndrewBenjamin, o usuário não está perguntando sobre procurar no arquivo e alterá-lo (todos os idiomas que conheço podem fazer isso); ele está perguntando sobre a inserção de texto, que é diferente de simplesmente alterar / substituir o que já está no arquivo. Talvez na aplicação prática seja diferente, mas nada que eu possa encontrar na API do ActionScript indica que ela se comporta de maneira diferente de qualquer outra linguagem nesse sentido.
eestrada
104

Depende do que você quer fazer. Para acrescentar, você pode abri-lo com "a":

 with open("foo.txt", "a") as f:
     f.write("new line\n")

Se você deseja antecipar algo, primeiro precisa ler o arquivo:

with open("foo.txt", "r+") as f:
     old = f.read() # read everything in the file
     f.seek(0) # rewind
     f.write("new line\n" + old) # write the new line before
Armin Ronacher
fonte
9
Apenas uma pequena adição, para usar a withinstrução no Python 2.5, você precisa adicionar "da importação futura com declaração". Fora isso, a abertura de arquivos com a withinstrução é definitivamente mais legível e menos suscetível a erros do que o fechamento manual.
Alexander Kojevnikov 24/09/08
2
Você pode considerar a fileinputbiblioteca auxiliar com a rotina aberta / leitura / modificação / gravação / substituição suja, ao usar o inline=Truearg. Exemplo aqui: stackoverflow.com/a/2363893/47390
mikegreenberg
3
Só não se esqueça de fechar o arquivo. f.Close()
D.Rosado 2/12/12
5
Não é um estilo que eu uso, D.Rosado, mas ao usar o estilo with, acho que você não precisa fechar manualmente. O with controla o recurso que ele cria.
21412 Chris
4
Você não precisa fechar manualmente o arquivo. Esse é o objetivo de usar "with" aqui. (Bem, na verdade, o Python faz isso assim que o objeto do arquivo é coletado como lixo, o que no CPython acontece quando o nome associado a ele sai do escopo ... mas outras implementações não, e o CPython pode parar de fazê-lo algum dia , então "com" é recomendado)
Jürgen A. Erhard
71

O fileinputmódulo da biblioteca padrão do Python reescreverá um arquivo no local se você usar o parâmetro inplace = 1:

import sys
import fileinput

# replace all occurrences of 'sit' with 'SIT' and insert a line after the 5th
for i, line in enumerate(fileinput.input('lorem_ipsum.txt', inplace=1)):
    sys.stdout.write(line.replace('sit', 'SIT'))  # replace 'sit' and write
    if i == 4: sys.stdout.write('\n')  # write a blank line after the 5th line
Dave
fonte
1
Como isso deve funcionar em python3? Acabei de portar um aplicativo que tinha algum código como este, de python para python3 e simplesmente não conseguia fazer com que isso funcionasse corretamente. A variável 'line' é do tipo bytes, tentei decodificá-la em unicode, modificá-la e codificá-la de volta para bytes, mas simplesmente não funcionaria corretamente. Isso levantou alguma exceção que não me lembro de nada. As pessoas que usam fileinput inplace = 1 no python3 obtiveram algum sucesso?
robru 21/02
1
@Robru: aqui está o código Python 3
jfs
13
Mas não há problema, porque você o testou primeiro em um arquivo sem importância, certo?
Paula Livingstone
33

A reescrita de um arquivo no local geralmente é feita salvando a cópia antiga com um nome modificado. O pessoal do Unix adiciona a ~para marcar o antigo. O pessoal do Windows faz todo tipo de coisa - adicione .bak ou .old - ou renomeie o arquivo completamente ou coloque ~ na frente do nome.

import shutil
shutil.move( afile, afile+"~" )

destination= open( aFile, "w" )
source= open( aFile+"~", "r" )
for line in source:
    destination.write( line )
    if <some condition>:
        destination.write( >some additional line> + "\n" )
source.close()
destination.close()

Em vez de shutil, você pode usar o seguinte.

import os
os.rename( aFile, aFile+"~" )
S.Lott
fonte
1
Parece bom. Pensando se .readlines () é melhor do que iterar a fonte?
bozdoz
2
@bozdoz: iterar é melhor, já que o readlines lê o arquivo inteiro. Não é bom para arquivos grandes. Obviamente, isso pressupõe que você pode fazer suas modificações de maneira localizada. Às vezes você não pode, ou seu código fica muito mais complicado.
Jürgen A. Erhard
@ S.Lott: os.rename(aFile, aFile + "~")modificará o nome do arquivo de origem, não criando uma cópia.
Patapoom
14

O módulo mmap do Python permitirá que você insira em um arquivo. O exemplo a seguir mostra como isso pode ser feito no Unix (o Windows mmap pode ser diferente). Observe que isso não lida com todas as condições de erro e você pode corromper ou perder o arquivo original. Além disso, isso não manipula seqüências de caracteres unicode.

import os
from mmap import mmap

def insert(filename, str, pos):
    if len(str) < 1:
        # nothing to insert
        return

    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(filename))
    origSize = m.size()

    # or this could be an error
    if pos > origSize:
        pos = origSize
    elif pos < 0:
        pos = 0

    m.resize(origSize + len(str))
    m[pos+len(str):] = m[pos:origSize]
    m[pos:pos+len(str)] = str
    m.close()
    f.close()

Também é possível fazer isso sem o mmap com os arquivos abertos no modo 'r +', mas é menos conveniente e menos eficiente, pois você precisará ler e armazenar temporariamente o conteúdo do arquivo da posição de inserção para o EOF - o que pode seja enorme.

mhawke
fonte
14

Conforme mencionado por Adam, você deve levar em consideração as limitações do sistema antes de poder decidir se possui memória suficiente para ler tudo na memória, substituir partes dele e reescrevê-lo.

Se você estiver lidando com um arquivo pequeno ou não tiver problemas de memória, isso pode ajudar:

Opção 1) Leia o arquivo inteiro na memória, faça uma substituição de regex no todo ou em parte da linha e substitua-o por essa linha mais a linha extra. Você precisará garantir que a 'linha do meio' seja única no arquivo ou, se você tiver registros de data e hora em cada linha, isso deve ser bastante confiável.

# open file with r+b (allow write and binary mode)
f = open("file.log", 'r+b')   
# read entire content of file into memory
f_content = f.read()
# basically match middle line and replace it with itself and the extra line
f_content = re.sub(r'(middle line)', r'\1\nnew line', f_content)
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(f_content)
# close file
f.close()

Opção 2) Descobrir a linha do meio e substituí-la por essa linha mais a linha extra.

# open file with r+b (allow write and binary mode)
f = open("file.log" , 'r+b')   
# get array of lines
f_content = f.readlines()
# get middle line
middle_line = len(f_content)/2
# overwrite middle line
f_content[middle_line] += "\nnew line"
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(''.join(f_content))
# close file
f.close()
Maxime R.
fonte
2

Escreveu uma classe pequena para fazer isso de forma limpa.

import tempfile

class FileModifierError(Exception):
    pass

class FileModifier(object):

    def __init__(self, fname):
        self.__write_dict = {}
        self.__filename = fname
        self.__tempfile = tempfile.TemporaryFile()
        with open(fname, 'rb') as fp:
            for line in fp:
                self.__tempfile.write(line)
        self.__tempfile.seek(0)

    def write(self, s, line_number = 'END'):
        if line_number != 'END' and not isinstance(line_number, (int, float)):
            raise FileModifierError("Line number %s is not a valid number" % line_number)
        try:
            self.__write_dict[line_number].append(s)
        except KeyError:
            self.__write_dict[line_number] = [s]

    def writeline(self, s, line_number = 'END'):
        self.write('%s\n' % s, line_number)

    def writelines(self, s, line_number = 'END'):
        for ln in s:
            self.writeline(s, line_number)

    def __popline(self, index, fp):
        try:
            ilines = self.__write_dict.pop(index)
            for line in ilines:
                fp.write(line)
        except KeyError:
            pass

    def close(self):
        self.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        with open(self.__filename,'w') as fp:
            for index, line in enumerate(self.__tempfile.readlines()):
                self.__popline(index, fp)
                fp.write(line)
            for index in sorted(self.__write_dict):
                for line in self.__write_dict[index]:
                    fp.write(line)
        self.__tempfile.close()

Então você pode usá-lo desta maneira:

with FileModifier(filename) as fp:
    fp.writeline("String 1", 0)
    fp.writeline("String 2", 20)
    fp.writeline("String 3")  # To write at the end of the file
ananth krishnan
fonte
Isso não funciona para mim pessoalmente, ele adiciona texto ao arquivo, mas remove tudo primeiro!
Bret Hawker
De fato, isso não funciona. Vergonha, porque parecia uma boa ideia.
Mario Krušelj 14/06/19
0

Se você conhece algum unix, tente o seguinte:

Notas: $ significa o prompt de comando

Digamos que você tenha um arquivo my_data.txt com o conteúdo da seguinte forma:

$ cat my_data.txt
This is a data file
with all of my data in it.

Em seguida, usando o osmódulo, você pode usar os sedcomandos usuais

import os

# Identifiers used are:
my_data_file = "my_data.txt"
command = "sed -i 's/all/none/' my_data.txt"

# Execute the command
os.system(command)

Se você não está ciente do sed, confira, é extremamente útil.

G. LC
fonte
3
Não é de forma alguma pitonico
#