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?
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?
Respostas:
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.
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
with
instrução no Python 2.5, você precisa adicionar "da importação futura com declaração". Fora isso, a abertura de arquivos com a with
instrução é definitivamente mais legível e menos suscetível a erros do que o fechamento manual.
fileinput
biblioteca auxiliar com a rotina aberta / leitura / modificação / gravação / substituição suja, ao usar o inline=True
arg. Exemplo aqui: stackoverflow.com/a/2363893/47390
f.Close()
O fileinput
mó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
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+"~" )
os.rename(aFile, aFile + "~")
modificará o nome do arquivo de origem, não criando uma cópia.
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.
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()
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
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 os
módulo, você pode usar os sed
comandos 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.