Pesquise e substitua uma linha em um arquivo em Python
Pergunta
Quero percorrer o conteúdo de um arquivo de texto e fazer uma pesquisa e substituir algumas linhas e gravar o resultado de volta no arquivo.Eu poderia primeiro carregar o arquivo inteiro na memória e depois gravá-lo de volta, mas essa provavelmente não é a melhor maneira de fazer isso.
Qual é a melhor maneira de fazer isso, dentro do código a seguir?
f = open(file)
for line in f:
if line.contains('foo'):
newline = line.replace('foo', 'bar')
# how to write this newline back to the file
Solução
Eu acho que algo assim deveria servir.Basicamente, ele grava o conteúdo em um novo arquivo e substitui o arquivo antigo pelo novo:
from tempfile import mkstemp
from shutil import move
from os import fdopen, remove
def replace(file_path, pattern, subst):
#Create temp file
fh, abs_path = mkstemp()
with fdopen(fh,'w') as new_file:
with open(file_path) as old_file:
for line in old_file:
new_file.write(line.replace(pattern, subst))
#Remove original file
remove(file_path)
#Move new file
move(abs_path, file_path)
Outras dicas
O caminho mais curto provavelmente seria usar o módulo de entrada de arquivo.Por exemplo, o seguinte adiciona números de linha a um arquivo, no local:
import fileinput
for line in fileinput.input("test.txt", inplace=True):
print "%d: %s" % (fileinput.filelineno(), line),
O que acontece aqui é:
- O arquivo original é movido para um arquivo de backup
- A saída padrão é redirecionada para o arquivo original dentro do loop
- Assim qualquer
print
instruções são gravadas de volta no arquivo original
fileinput
tem mais sinos e assobios.Por exemplo, pode ser usado para operar automaticamente em todos os arquivos em sys.args[1:]
, sem que você precise iterá-los explicitamente.A partir do Python 3.2 ele também fornece um gerenciador de contexto conveniente para uso em um with
declaração.
Enquanto fileinput
é ótimo para scripts descartáveis, eu teria cuidado ao usá-lo em código real porque, reconhecidamente, não é muito legível ou familiar.Em código real (de produção), vale a pena gastar apenas mais algumas linhas de código para tornar o processo explícito e, assim, tornar o código legível.
Existem duas opções:
- O arquivo não é muito grande e você pode lê-lo completamente na memória.Em seguida, feche o arquivo, reabra-o no modo de escrita e grave novamente o conteúdo modificado.
- O arquivo é muito grande para ser armazenado na memória;você pode movê-lo para um arquivo temporário e abri-lo, lendo linha por linha e gravando novamente no arquivo original.Observe que isso requer o dobro do armazenamento.
Aqui está outro exemplo que foi testado e corresponderá aos padrões de pesquisa e substituição:
import fileinput
import sys
def replaceAll(file,searchExp,replaceExp):
for line in fileinput.input(file, inplace=1):
if searchExp in line:
line = line.replace(searchExp,replaceExp)
sys.stdout.write(line)
Exemplo de uso:
replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")
Isso deve funcionar:(edição local)
import fileinput
# Does a list of files, and
# redirects STDOUT to the file in question
for line in fileinput.input(files, inplace = 1):
print line.replace("foo", "bar"),
Baseado na resposta de Thomas Watnedal.No entanto, isso não responde exatamente à parte linha a linha da pergunta original.A função ainda pode substituir linha a linha
Esta implementação substitui o conteúdo do arquivo sem usar arquivos temporários, como consequência as permissões do arquivo permanecem inalteradas.
Além disso, re.sub em vez de substituir, permite a substituição de regex em vez de apenas substituição de texto simples.
Ler o arquivo como uma única string em vez de linha por linha permite a correspondência e substituição de várias linhas.
import re
def replace(file, pattern, subst):
# Read contents from file as a single string
file_handle = open(file, 'r')
file_string = file_handle.read()
file_handle.close()
# Use RE package to allow for replacement (also allowing for (multiline) REGEX)
file_string = (re.sub(pattern, subst, file_string))
# Write contents to file.
# Using mode 'w' truncates the file.
file_handle = open(file, 'w')
file_handle.write(file_string)
file_handle.close()
Como sugere lassevk, escreva o novo arquivo conforme você avança, aqui está um exemplo de código:
fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
Se você deseja uma função genérica que substitua qualquer texto com algum outro texto, este é provavelmente o melhor caminho a seguir, especialmente se você é fã de regex:
import re
def replace( filePath, text, subs, flags=0 ):
with open( filePath, "r+" ) as file:
fileContents = file.read()
textPattern = re.compile( re.escape( text ), flags )
fileContents = textPattern.sub( subs, fileContents )
file.seek( 0 )
file.truncate()
file.write( fileContents )
Uma maneira mais Python seria usar gerenciadores de contexto como o código abaixo:
from tempfile import mkstemp
from shutil import move
from os import remove
def replace(source_file_path, pattern, substring):
fh, target_file_path = mkstemp()
with open(target_file_path, 'w') as target_file:
with open(source_file_path, 'r') as source_file:
for line in source_file:
target_file.write(line.replace(pattern, substring))
remove(source_file_path)
move(target_file_path, source_file_path)
Você pode encontrar o trecho completo aqui.
Crie um novo arquivo, copie as linhas do antigo para o novo e faça a substituição antes de gravar as linhas no novo arquivo.
Expandindo a resposta de @Kiran, que concordo ser mais sucinta e Pythonic, adiciona codecs para suportar a leitura e gravação de UTF-8:
import codecs
from tempfile import mkstemp
from shutil import move
from os import remove
def replace(source_file_path, pattern, substring):
fh, target_file_path = mkstemp()
with codecs.open(target_file_path, 'w', 'utf-8') as target_file:
with codecs.open(source_file_path, 'r', 'utf-8') as source_file:
for line in source_file:
target_file.write(line.replace(pattern, substring))
remove(source_file_path)
move(target_file_path, source_file_path)
Usando a resposta do hamishmcn como modelo, consegui procurar uma linha em um arquivo que correspondesse ao meu regex e substituí-la por uma string vazia.
import re
fin = open("in.txt", 'r') # in file
fout = open("out.txt", 'w') # out file
for line in fin:
p = re.compile('[-][0-9]*[.][0-9]*[,]|[-][0-9]*[,]') # pattern
newline = p.sub('',line) # replace matching strings with empty string
print newline
fout.write(newline)
fin.close()
fout.close()
se você remover o recuo como abaixo, ele irá pesquisar e substituir em várias linhas.Veja abaixo, por exemplo.
def replace(file, pattern, subst):
#Create temp file
fh, abs_path = mkstemp()
print fh, abs_path
new_file = open(abs_path,'w')
old_file = open(file)
for line in old_file:
new_file.write(line.replace(pattern, subst))
#close temp file
new_file.close()
close(fh)
old_file.close()
#Remove original file
remove(file)
#Move new file
move(abs_path, file)
Para usuários Linux:
import os
os.system('sed -i \'s/foo/bar/\' '+file_path)