Domanda

Voglio scorrere il contenuto di un file di testo ed eseguire una ricerca e sostituzione 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, all'interno del 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
È stato utile?

Soluzione

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

Altri suggerimenti

La via più breve sarebbe probabilmente quella di utilizzare il file modulo di input file.Ad esempio, quanto segue aggiunge i numeri di riga a un file, sul posto:

import fileinput

for line in fileinput.input("test.txt", inplace=True):
    print "%d: %s" % (fileinput.filelineno(), line),

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. Quindi qualsiasi print istruzioni riscrivono nel file originale

fileinput ha più campane e fischietti.Ad esempio, può essere utilizzato per operare automaticamente su tutti i file in sys.args[1:], senza dover ripetere esplicitamente su di essi.A partire da Python 3.2 fornisce anche un comodo gestore di contesto da utilizzare in a with dichiarazione.


Mentre fileinput è ottimo per gli script usa e getta, sarei cauto nell'usarlo nel codice reale perché, ammetto, non è molto leggibile o familiare.Nel codice reale (di produzione) vale la pena spendere solo qualche riga di codice in più per rendere esplicito il processo e quindi rendere leggibile il codice.

Ci sono due opzioni:

  1. Il file non è eccessivamente grande e puoi semplicemente leggerlo interamente in memoria.Quindi chiudi il file, riaprilo in modalità scrittura e riscrivi il contenuto modificato.
  2. Il file è troppo grande per essere archiviato in memoria;puoi spostarlo in un file temporaneo e aprirlo, leggendolo riga per riga, riscrivendo nel file originale.Tieni presente che ciò richiede il doppio dello spazio di archiviazione.

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

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

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

Questa implementazione sostituisce il contenuto del file senza utilizzare file temporanei, di conseguenza i permessi dei file rimangono invariati.

Inoltre re.sub invece di sostituire, consente la sostituzione dell'espressione regolare invece della sola sostituzione del testo normale.

La lettura del file come singola stringa 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()

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

Se desideri una funzione generica che sostituisca Qualunque testo con altro testo, questo è probabilmente il modo migliore di procedere, soprattutto se sei un fan delle espressioni regolari:

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 )

Un modo più pitonico sarebbe quello di utilizzare 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.

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

Espandendo la risposta di @ Kiran, che concordo sia più concisa e pythonica, 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)

Usando la risposta di hamishmcn come modello sono stato in grado di cercare una riga in un file che corrispondesse alla mia regex e sostituirla 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()

se rimuovi il rientro come di seguito, verrà cercato e sostituito 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)

Per gli utenti Linux:

import os
os.system('sed -i \'s/foo/bar/\' '+file_path)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top