Como pesquisar e substituir texto em um arquivo?

212

Como pesquiso e substituo o texto em um arquivo usando o Python 3?

Aqui está o meu código:

import os
import sys
import fileinput

print ("Text to search for:")
textToSearch = input( "> " )

print ("Text to replace it with:")
textToReplace = input( "> " )

print ("File to perform Search-Replace on:")
fileToSearch  = input( "> " )
#fileToSearch = 'D:\dummy1.txt'

tempFile = open( fileToSearch, 'r+' )

for line in fileinput.input( fileToSearch ):
    if textToSearch in line :
        print('Match Found')
    else:
        print('Match Not Found!!')
    tempFile.write( line.replace( textToSearch, textToReplace ) )
tempFile.close()


input( '\n\n Press Enter to exit...' )

Arquivo de entrada:

hi this is abcd hi this is abcd
This is dummy text file.
This is how search and replace works abcd

Quando eu procuro e substituo 'ram' por 'abcd' no arquivo de entrada acima, ele funciona como um encanto. Mas quando eu faço isso vice-versa, ou seja, substituindo 'abcd' por 'ram', alguns caracteres indesejados são deixados no final.

Substituindo 'abcd' por 'ram'

hi this is ram hi this is ram
This is dummy text file.
This is how search and replace works rambcd
Shriram
fonte
Você pode ser um pouco mais específico quando diz "alguns caracteres indesejados são deixados no final", o que você vê?
precisa
Atualizado a pergunta com a saída que eu tenho.
Shriram

Respostas:

241

fileinputjá suporta edição no local. Ele redireciona stdoutpara o arquivo neste caso:

#!/usr/bin/env python3
import fileinput

with fileinput.FileInput(filename, inplace=True, backup='.bak') as file:
    for line in file:
        print(line.replace(text_to_search, replacement_text), end='')
jfs
fonte
13
O que o end=''argumento deveria fazer?
egpbos
18
linejá tem uma nova linha. endé uma nova linha por padrão, end=''faz com que print()a função não imprimem nova linha adicional
JFS
11
Não use fileinput! Considere escrever o código para fazer isso sozinho. Redirecionar sys.stdout não é uma ótima idéia, especialmente se você estiver fazendo isso sem tentar ... finalmente como o fileinput. Se uma exceção for gerada, seu stdout poderá nunca ser restaurado.
craigds
9
@ craigds: errado. fileinputnão é uma ferramenta para todos os trabalhos ( nada é), mas há muitos casos em que é a ferramenta certa, por exemplo, para implementar um sedfiltro semelhante no Python. Não use uma chave de fenda para bater nas unhas.
JFS
5
Se você realmente deseja redirecionar o stdout para o seu arquivo por algum motivo, não é difícil fazê-lo melhor do que fileinputfaz (basicamente, use try..finallyou um gerenciador de contexto para garantir que você retorne o stdout ao seu valor original posteriormente). O código-fonte para fileinputé bastante horrível, e faz algumas coisas realmente inseguras. Se ele fosse escrito hoje, duvido muito que ele fosse incluído no stdlib.
craigds
333

Conforme apontado por michaelb958, você não pode substituir no lugar por dados de comprimento diferente, pois isso colocará o restante das seções fora do lugar. Discordo dos outros pôsteres que sugerem que você leia um arquivo e escreva para outro. Em vez disso, eu lia o arquivo na memória, corrigia os dados e os escrevia no mesmo arquivo em uma etapa separada.

# Read in the file
with open('file.txt', 'r') as file :
  filedata = file.read()

# Replace the target string
filedata = filedata.replace('ram', 'abcd')

# Write the file out again
with open('file.txt', 'w') as file:
  file.write(filedata)

A menos que você tenha um arquivo grande para trabalhar, grande demais para carregar na memória de uma só vez, ou esteja preocupado com a perda potencial de dados, se o processo for interrompido durante a segunda etapa na qual você grava dados no arquivo.

Jack Aidley
fonte
5
with file = open(..):não é válido Python ( =), embora a intenção seja clara. .replace()não modifica a sequência (é imutável), portanto você precisa usar o valor retornado. De qualquer forma, o código que suporta arquivos grandes pode ser ainda mais simples, a menos que você precise pesquisar e substituir texto que se estende por várias linhas.
JFS
40
Você está certo, e que - as pessoas - é por isso que você deve testar o código antes embaraçoso si mesmo na internet;)
Jack Aidley
19
@JonasStein: Não, não deveria. A withinstrução fecha automaticamente o arquivo no final do bloco de instruções.
Jack Aidley
2
@ JackAidley que é interessante. Obrigado pela explicação.
Jonas Stein
4
@JackAidley, porque é curto, simples, fácil de usar e entender, e resolve um problema real que muitas pessoas têm (e, portanto, que muitas pessoas pesquisam - encontrando assim sua resposta).
Ben Barden
52

Como Jack Aidley postou e JF Sebastian apontou, este código não funcionará:

 # Read in the file
filedata = None
with file = open('file.txt', 'r') :
  filedata = file.read()

# Replace the target string
filedata.replace('ram', 'abcd')

# Write the file out again
with file = open('file.txt', 'w') :
  file.write(filedata)`

Mas esse código funcionará (eu testei):

f = open(filein,'r')
filedata = f.read()
f.close()

newdata = filedata.replace("old data","new data")

f = open(fileout,'w')
f.write(newdata)
f.close()

Usando esse método, filein e fileout podem ser o mesmo arquivo, porque o Python 3.3 substituirá o arquivo na abertura para gravação.

Neamerjell
fonte
9
Eu acredito que a diferença está aqui: filedata.replace ('ram', 'abcd') Em comparação com: newdata = filedata.replace ("dados antigos", "novos dados") Nada a ver com a declaração "with"
Diegomanas
5
1. por que você removeria with-statement? 2. Como indicado na minha resposta, fileinputpode funcionar no local - ele pode substituir os dados no mesmo arquivo (ele usa um arquivo temporário internamente). A diferença é que fileinputnão é necessário carregar o arquivo inteiro na memória.
JFS
8
Apenas para salvar outras pessoas que revisitam a resposta de Jack Aidley, ela foi corrigida desde essa resposta; portanto, essa é agora redundante (e inferior devido à perda de withblocos).
Chris
46

Você pode fazer a substituição assim

f1 = open('file1.txt', 'r')
f2 = open('file2.txt', 'w')
for line in f1:
    f2.write(line.replace('old_text', 'new_text'))
f1.close()
f2.close()
Jayram
fonte
7

Você também pode usar pathlib.

from pathlib2 import Path
path = Path(file_to_search)
text = path.read_text()
text = text.replace(text_to_search, replacement_text)
path.write_text(text)
Yuya Takashina
fonte
Obrigado Yuya. A solução acima funcionou bem. Nota: Você precisa primeiro fazer backup do seu arquivo original, pois ele substitui o arquivo original. Se você deseja substituir repetidamente o texto, pode continuar adicionando as duas últimas linhas, como abaixo. text = text.replace (texto_a_search, texto_de_ substituição) path.write_text (texto)
Nages
3

Com um único bloco, você pode pesquisar e substituir seu texto:

with open('file.txt','r+') as f:
    filedata = f.read()
    filedata = filedata.replace('abc','xyz')
    f.truncate(0)
    f.write(filedata)
iknowitwasyoufredo
fonte
1
Você esqueceu seeko início do arquivo antes de escrevê-lo. truncatenão faz isso e, portanto, você terá lixo no arquivo.
ur.
2

Seu problema decorre da leitura e gravação no mesmo arquivo. Em vez de abrir fileToSearchpara escrever, abra um arquivo temporário real e, depois de terminar e fechar tempFile, use os.renamepara mover o novo arquivo fileToSearch.

icktoofay
fonte
1
FYI amigável (sinta-se à vontade para editar a resposta): A causa raiz não está conseguindo reduzir o meio de um arquivo no local. Ou seja, se você pesquisar 5 caracteres e substituir por 3, os 3 primeiros caracteres dos 5 pesquisados ​​serão substituídos; mas os outros 2 não podem ser removidos, eles simplesmente ficam lá. A solução de arquivo temporário remove esses caracteres "restantes" soltando-os em vez de gravá-los no arquivo temporário.
michaelb958 - GoFundMonica
2

(instalação pip python-util)

from pyutil import filereplace

filereplace("somefile.txt","abcd","ram")

O segundo parâmetro (o item a ser substituído, por exemplo, "abcd" também pode ser um regex)
substituirá todas as ocorrências

MisterL2
fonte
Eu tive uma experiência ruim com isso (ele adicionou alguns caracteres ao final do arquivo), por isso não posso recomendá-lo, mesmo que uma única linha seja legal.
Azrael3000 16/06
@ Azrael3000 Adicionou caracteres? Eu não vi isso acontecer comigo. Eu apreciaria muito se você abriu um problema no Github para que eu possa corrigi-lo github.com/MisterL2/python-util
MisterL2
1

Minha variante, uma palavra de cada vez no arquivo inteiro.

Eu li na memória.

def replace_word(infile,old_word,new_word):
    if not os.path.isfile(infile):
        print ("Error on replace_word, not a regular file: "+infile)
        sys.exit(1)

    f1=open(infile,'r').read()
    f2=open(infile,'w')
    m=f1.replace(old_word,new_word)
    f2.write(m)
LiPi
fonte
0

Eu fiz isso:

#!/usr/bin/env python3

import fileinput
import os

Dir = input ("Source directory: ")
os.chdir(Dir)

Filelist = os.listdir()
print('File list: ',Filelist)

NomeFile = input ("Insert file name: ")

CarOr = input ("Text to search: ")

CarNew = input ("New text: ")

with fileinput.FileInput(NomeFile, inplace=True, backup='.bak') as file:
    for line in file:
        print(line.replace(CarOr, CarNew), end='')

file.close ()
Zelmik
fonte
Triste, mas fileinput não trabalha inplace=Truecom utf-8.
Sergio
0

Modifiquei levemente a postagem de Jayram Singh para substituir todas as instâncias de um '!' caractere para um número que eu queria incrementar com cada instância. Achei que poderia ser útil para alguém que quisesse modificar um personagem que ocorresse mais de uma vez por linha e quisesse iterar. Espero que ajude alguém. PS- Eu sou muito novo em codificação, então peço desculpas se minha postagem for inadequada de alguma forma, mas isso funcionou para mim.

f1 = open('file1.txt', 'r')
f2 = open('file2.txt', 'w')
n = 1  

# if word=='!'replace w/ [n] & increment n; else append same word to     
# file2

for line in f1:
    for word in line:
        if word == '!':
            f2.write(word.replace('!', f'[{n}]'))
            n += 1
        else:
            f2.write(word)
f1.close()
f2.close()
Doc5506
fonte
0
def word_replace(filename,old,new):
    c=0
    with open(filename,'r+',encoding ='utf-8') as f:
        a=f.read()
        b=a.split()
        for i in range(0,len(b)):
            if b[i]==old:
                c=c+1
        old=old.center(len(old)+2)
        new=new.center(len(new)+2)
        d=a.replace(old,new,c)
        f.truncate(0)
        f.seek(0)
        f.write(d)
    print('All words have been replaced!!!')
Vinit Pillai
fonte
Este código substituirá a palavra que você pretende. o único problema é que reescreve o arquivo inteiro. pode ficar preso se o arquivo for muito longo para o processador suportar.
Vinit Pillai
0

Igual a:

def find_and_replace(file, word, replacement):
  with open(file, 'r+') as f:
    text = f.read()
    f.write(text.replace(word, replacement))
Cyril Alohan
fonte
Certifique-se de que sua resposta melhore com outras respostas já presentes nesta pergunta.
hongsy 17/01
Isso acrescentará o texto com a substituição no final do arquivo. Na minha opinião, @Jack Aidley aswer é exatamente o que OP significava stackoverflow.com/a/17141572/6875391
Kirill
-3
def findReplace(find, replace):

    import os 

    src = os.path.join(os.getcwd(), os.pardir) 

    for path, dirs, files in os.walk(os.path.abspath(src)):

        for name in files: 

            if name.endswith('.py'): 

                filepath = os.path.join(path, name)

                with open(filepath) as f: 

                    s = f.read()

                s = s.replace(find, replace) 

                with open(filepath, "w") as f:

                    f.write(s) 
Deepak G
fonte