Cerca e sostituisci una riga in un file in Python


293

Voglio passare in rassegna il contenuto di un file di testo e fare una ricerca e sostituire su alcune righe e riscrivere il risultato nel file. Potrei prima caricare l'intero file in memoria e poi riscriverlo, ma probabilmente non è il modo migliore per farlo.

Qual è il modo migliore per farlo, nel seguente codice?

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

Risposte:


192

Immagino che qualcosa del genere dovrebbe farlo. Fondamentalmente scrive il contenuto in un nuovo file e sostituisce il vecchio file con il nuovo file:

from tempfile import mkstemp
from shutil import move, copymode
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))
    #Copy the file permissions from the old file to the new file
    copymode(file_path, abs_path)
    #Remove original file
    remove(file_path)
    #Move new file
    move(abs_path, file_path)

5
Solo un piccolo commento: filesta oscurando la classe predefinita con lo stesso nome.
ezdazuzena,

4
Questo codice modifica le autorizzazioni sul file originale. Come posso conservare le autorizzazioni originali?
Nic

1
qual è il punto di fh, lo usi nella chiamata ravvicinata ma non vedo il punto di creare un file solo per chiuderlo ...
Wicelo,

2
@Wicelo È necessario chiuderlo per evitare perdite del descrittore di file. Ecco una spiegazione decente: logilab.org/17873
Thomas Watnedal,

1
Sì, ho scoperto che mkstemp()sta restituendo una 2 tupla e (fh, abs_path) = fh, abs_pathnon lo sapevo quando ho posto la domanda.
Wicelo,

272

Il modo più breve sarebbe probabilmente quello di utilizzare il modulo fileinput . Ad esempio, quanto segue aggiunge numeri di riga a un file, sul posto:

import fileinput

for line in fileinput.input("test.txt", inplace=True):
    print('{} {}'.format(fileinput.filelineno(), line), end='') # for Python 3
    # print "%d: %s" % (fileinput.filelineno(), line), # for Python 2

Quello che succede qui è:

  1. Il file originale viene spostato in un file di backup
  2. L'output standard viene reindirizzato al file originale all'interno del ciclo
  3. Pertanto, qualsiasi printistruzione riscrive nel file originale

fileinputha più campane e fischietti. Ad esempio, può essere utilizzato per operare automaticamente su tutti i file in sys.args[1:], senza che tu debba iterarli esplicitamente. A partire da Python 3.2 fornisce anche un comodo gestore di contesto da utilizzare in withun'istruzione.


Anche se fileinputè ottimo per gli script usa e getta, sarei diffidente nell'usarlo nel vero codice perché è vero che non è molto leggibile o familiare. Nel codice reale (di produzione) vale la pena spendere solo poche righe di codice per rendere esplicito il processo e rendere quindi leggibile il codice.

Esistono due opzioni:

  1. Il file non è eccessivamente grande e puoi semplicemente leggerlo interamente in memoria. Quindi chiudere il file, riaprirlo in modalità di scrittura e riscrivere il contenuto modificato.
  2. Il file è troppo grande per essere archiviato in memoria; puoi spostarlo su un file temporaneo e aprirlo, leggendolo riga per riga, riscrivendolo nel file originale. Si noti che ciò richiede il doppio della memoria.

13
So che questo ha solo due righe, tuttavia non credo che il codice sia molto espressivo in sé. Perché se pensi per un secondo, se non conosci la funzione, ci sono pochissimi indizi su ciò che sta succedendo. Stampare il numero di riga e la riga non è lo stesso di scriverlo ... se hai capito bene ...
Chutsu,

14
Questo FA scrittura al file. Reindirizza stdout al file. Dai un'occhiata ai documenti
brice

32
Il bit chiave qui è la virgola alla fine dell'istruzione print: sorprende l'istruzione print aggiungendo un'altra riga (poiché la riga ne ha già una). Tuttavia, non è affatto ovvio (motivo per cui Python 3 ha cambiato quella sintassi, per fortuna).
VPeric,

4
Si noti che non funziona quando si fornisce un hook di apertura al file, ad esempio quando si tenta di leggere / scrivere file codificati UTF-16.
bompf,

5
Per python3,print(line, end='')
Ch.Idea,

80

Ecco un altro esempio che è stato testato e corrisponderà ai modelli di ricerca e sostituzione:

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)

Esempio di utilizzo:

replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")

23
L'uso di esempio fornisce un'espressione regolare, ma searchExp in linené lo line.replacesono né le operazioni di espressione regolare. Sicuramente l'uso dell'esempio è sbagliato.
Kojiro,

Invece di if searchExp in line: line = line.replace(searchExp, replaceExpr)te puoi semplicemente scrivere line = line.replace(searchExp, replaceExpr). Non viene generata alcuna eccezione, la linea rimane invariata.
David Wallace,

Ha funzionato perfettamente anche per me. Mi ero imbattuto in numerosi altri esempi molto simili a questo, ma il trucco era l'uso di sys.stdout.write(line). Grazie ancora!
Salvia,

Se lo uso, il mio file diventa vuoto. Qualche idea?
Javier López Tomás,

Sto usando questo
Rakib Fiha il

64

Questo dovrebbe funzionare: (modifica sul posto)

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"),

5
+1. Inoltre, se ricevi un RuntimeError: input () già attivo, chiama il fileinput.close ()
geographika il

1
Nota che filesdovrebbe essere una stringa contenente il nome del file, non un oggetto file .
atomh33ls,

9
stampa aggiunge una nuova riga che potrebbe già essere lì. per evitarlo, aggiungi .rstrip () alla fine dei tuoi rimpiazzi
Guillaume Gendre,

Invece usa i file arg in input (), potrebbe essere fileinput.input (inplace = 1) e chiamare lo script come> python
replace.py myfiles

24

Basato sulla risposta di Thomas Watnedal. Tuttavia, ciò non risponde esattamente alla parte line-to-line della domanda originale. La funzione può comunque essere sostituita da riga a riga

Questa implementazione sostituisce il contenuto del file senza utilizzare file temporanei, di conseguenza le autorizzazioni dei file rimangono invariate.

Inoltre re.sub invece di sostituire, consente la sostituzione regex anziché solo la sostituzione in testo normale.

La lettura del file come stringa singola anziché riga per riga consente la corrispondenza e la sostituzione su più righe.

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()

2
Potresti voler utilizzare rbe wbattributi quando apri i file poiché ciò preserverà le terminazioni di riga originali
Nux,

In Python 3, non puoi usare 'wb' e 'rb' con 're'. Verrà visualizzato l'errore "TypeError: impossibile utilizzare un modello di stringa su un oggetto simile a byte"

15

Come suggerisce lassevk, scrivi il nuovo file mentre procedi, ecco un esempio di codice:

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()

12

Se vuoi una funzione generica che sostituisca qualsiasi testo con qualche altro testo, questo è probabilmente il modo migliore per andare, in particolare se sei un fan di 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 )

12

Un modo più pitonico sarebbe usare i gestori di contesto come il codice seguente:

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)

Puoi trovare lo snippet completo qui .


In Python> = 3.1 è possibile aprire i due gestori di contesto sulla stessa riga .
florisla,

4

Crea un nuovo file, copia le righe dal vecchio al nuovo ed esegui la sostituzione prima di scrivere le righe nel nuovo file.


4

Espandendo la risposta di @ Kiran, che concordo è più succinta e Pythonic, questo aggiunge codec per supportare la lettura e la scrittura di 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)

Conserverà l'autorizzazione del vecchio file nel nuovo file?
Bidyut,

2

Usando la risposta di hamishmcn come modello sono stato in grado di cercare una riga in un file che corrispondesse al mio regex e di sostituirlo con una stringa vuota.

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()

1
Dovresti compilare la regex FUORI il ciclo for, altrimenti è uno spreco di prestazioni
Axel

2

fileinput è abbastanza semplice come menzionato nelle risposte precedenti:

import fileinput

def replace_in_file(file_path, search_text, new_text):
    with fileinput.input(file_path, inplace=True) as f:
        for line in f:
            new_line = line.replace(search_text, new_text)
            print(new_line, end='')

Spiegazione:

  • fileinputposso accettare più file, ma preferisco chiudere ogni singolo file non appena viene elaborato. Quindi messo single file_pathin withdichiarazione.
  • printL'istruzione non stampa nulla quando inplace=True, poiché STDOUTviene inoltrata al file originale.
  • end=''nel printcomunicato è quello di eliminare intermedi nuove righe vuote.

Può essere usato come segue:

file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')

0

se rimuovi il rientro nel modo seguente, cercherà e sostituirà su più righe. Vedi sotto per esempio.

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)

La formattazione di questo codice Python non sembra del tutto giusta ... (Ho cercato di risolvere, ma non ero sicuro di cosa fosse previsto)
Andy Hayden,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.