Cari dan ganti baris dalam file dengan Python

293

Saya ingin mengulang isi file teks dan melakukan pencarian dan mengganti beberapa baris dan menulis hasilnya kembali ke file. Saya pertama-tama dapat memuat seluruh file dalam memori dan kemudian menulisnya kembali, tetapi itu mungkin bukan cara terbaik untuk melakukannya.

Apa cara terbaik untuk melakukan ini, dalam kode berikut?

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
pkit
sumber

Jawaban:

191

Saya kira sesuatu seperti ini harus dilakukan. Ini pada dasarnya menulis konten ke file baru dan mengganti file lama dengan file baru:

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)
Thomas Watnedal
sumber
5
Hanya komentar kecil: filemembayangi kelas yang telah ditentukan dengan nama yang sama.
ezdazuzena
4
Kode ini mengubah izin pada file asli. Bagaimana saya bisa menjaga izin asli?
nic
1
apa gunanya fh, Anda menggunakannya dalam panggilan dekat tapi saya tidak melihat gunanya membuat file hanya untuk menutupnya ...
Wicelo
2
@Wicelo Anda harus menutupnya untuk mencegah bocornya deskriptor file. Berikut adalah penjelasan yang layak: logilab.org/17873
Thomas Watnedal
1
Ya saya telah menemukan yang mkstemp()mengembalikan 2-tupel dan (fh, abs_path) = fh, abs_path, saya tidak tahu ketika saya mengajukan pertanyaan.
Wicelo
272

Cara terpendek mungkin adalah dengan menggunakan modul fileinput . Misalnya, berikut ini menambahkan nomor baris ke file, di tempat:

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

Yang terjadi di sini adalah:

  1. File asli dipindahkan ke file cadangan
  2. Output standar diarahkan ke file asli dalam loop
  3. Jadi setiap printpernyataan menulis kembali ke file asli

fileinputmemiliki lebih banyak bel dan peluit. Misalnya, ini dapat digunakan untuk beroperasi secara otomatis pada semua file di sys.args[1:], tanpa Anda harus mengulanginya secara eksplisit. Dimulai dengan Python 3.2 itu juga menyediakan manajer konteks yang nyaman untuk digunakan dalam withpernyataan.


Walaupun fileinputbagus untuk skrip yang dapat dibuang, saya akan berhati-hati menggunakannya dalam kode nyata karena memang itu tidak mudah dibaca atau tidak asing. Dalam kode (produksi) yang sebenarnya, sebaiknya hanya menghabiskan beberapa baris kode lagi untuk menjadikan prosesnya eksplisit dan dengan demikian membuat kode tersebut dapat dibaca.

Ada dua opsi:

  1. File tidak terlalu besar, dan Anda bisa membacanya sepenuhnya ke memori. Kemudian tutup file, buka kembali dalam mode penulisan dan tulis kembali konten yang dimodifikasi.
  2. File terlalu besar untuk disimpan dalam memori; Anda dapat memindahkannya ke file sementara dan membukanya, membacanya baris demi baris, menulis kembali ke file asli. Perhatikan bahwa ini membutuhkan penyimpanan dua kali lipat.
Eli Bendersky
sumber
13
Saya tahu ini hanya memiliki dua baris di dalamnya, namun saya tidak berpikir kode itu sangat ekspresif. Karena jika Anda berpikir sebentar, jika Anda tidak tahu fungsinya, ada sangat sedikit petunjuk tentang apa yang sedang terjadi. Mencetak nomor baris dan baris tidak sama dengan menulisnya ... jika Anda mendapatkan intisari ...
chutsu
14
Ini TIDAK menulis ke file. Ini mengarahkan stdout ke file. Lihatlah dokumennya
brice
32
Bit kunci di sini adalah koma di akhir pernyataan cetak: itu menekan pernyataan cetak menambahkan baris baru (karena baris sudah memiliki satu). Ini tidak terlalu jelas sama sekali, (itulah sebabnya Python 3 mengubah sintaks itu, untungnya cukup).
VPeric
4
Harap perhatikan bahwa ini tidak berfungsi ketika Anda memberikan kait pembuka ke file tersebut, misalnya ketika Anda mencoba membaca / menulis file yang disandikan UTF-16.
bompf
5
Untuk python3,print(line, end='')
Ch.Idea
80

Berikut contoh lain yang telah diuji, dan akan cocok dengan pola pencarian & ganti:

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)

Contoh penggunaan:

replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")
Jason
sumber
23
Contoh penggunaan menyediakan ekspresi reguler, tetapi tidak searchExp in linejuga line.replaceoperasi ekspresi reguler. Tentunya contoh penggunaannya salah.
kojiro
Alih-alih if searchExp in line: line = line.replace(searchExp, replaceExpr)Anda bisa menulis line = line.replace(searchExp, replaceExpr). Tidak terkecuali yang dihasilkan, garis tetap tidak berubah.
David Wallace
Bekerja dengan baik untuk saya juga. Saya telah menemukan sejumlah contoh lain yang terlihat sangat mirip dengan ini, tetapi triknya adalah penggunaan sys.stdout.write(line). Terima kasih lagi!
Sage
Jika saya menggunakan ini, file saya menjadi kosong. Ada ide?
Javier López Tomás
Saya menggunakan ini
Rakib Fiha
64

Ini harus bekerja: (inplace editing)

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"),
Kinlan
sumber
5
+1. Juga jika Anda menerima RuntimeError: input () sudah aktif maka panggil fileinput.close ()
geografi
1
Perhatikan bahwa filesharus berupa string yang berisi nama file, bukan objek file .
atomh33ls
9
print menambahkan baris baru yang sudah bisa ada di sana. untuk menghindari ini, tambahkan .rstrip () di akhir penggantian Anda
Guillaume Gendre
Alih-alih menggunakan file arg di input (), itu bisa menjadi fileinput.input (inplace = 1) dan memanggil skrip sebagai> python replace.py myfiles * .txt
chespinoza
24

Berdasarkan jawaban oleh Thomas Watnedal. Namun, ini tidak menjawab bagian line-to-line dari pertanyaan aslinya. Fungsi masih dapat menggantikan secara garis-ke-garis

Implementasi ini menggantikan konten file tanpa menggunakan file sementara, karena itu izin file tetap tidak berubah.

Juga ganti alih-alih ganti, izinkan penggantian regex alih-alih hanya penggantian teks biasa.

Membaca file sebagai string tunggal dan bukannya baris demi baris memungkinkan untuk pencocokan dan penggantian multiline.

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()
Terima kasih
sumber
2
Anda mungkin ingin menggunakan rbdan wbatribut ketika membuka file karena ini akan mempertahankan akhir baris asli
Nux
Dalam Python 3, Anda tidak bisa menggunakan 'wb' dan 'rb' dengan 're'. Ini akan memberikan kesalahan "TypeError: tidak dapat menggunakan pola string pada objek byte-seperti"
15

Seperti yang disarankan lassevk, tuliskan file baru saat Anda mulai, berikut adalah beberapa contoh kode:

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

Jika Anda menginginkan fungsi generik yang menggantikan teks apa pun dengan teks lain, ini kemungkinan merupakan cara terbaik, terutama jika Anda seorang penggemar 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 )
starryknight64
sumber
12

Cara yang lebih pythonic adalah dengan menggunakan manajer konteks seperti kode di bawah ini:

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)

Anda dapat menemukan cuplikan lengkapnya di sini .

Kiran
sumber
Dalam Python> = 3.1 Anda bisa membuka dua manajer konteks pada baris yang sama .
florisla
4

Buat file baru, salin baris dari yang lama ke yang baru, dan lakukan penggantian sebelum Anda menulis baris ke file baru.

Lasse V. Karlsen
sumber
4

Memperluas jawaban @ Kiran, yang saya setujui lebih ringkas dan Pythonic, ini menambahkan codec untuk mendukung pembacaan dan penulisan 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)
aliran ignite
sumber
Apakah akan mempertahankan izin dari file lama di file baru?
Bidyut
2

Menggunakan jawaban hamishmcn sebagai templat, saya bisa mencari baris dalam file yang cocok dengan regex saya dan menggantinya dengan string kosong.

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()
Emmanuel
sumber
1
Anda harus mengkompilasi regex DI LUAR for loop, jika tidak merupakan pemborosan kinerja
Axel
2

fileinput cukup mudah seperti yang disebutkan pada jawaban sebelumnya:

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

Penjelasan:

  • fileinputdapat menerima beberapa file, tetapi saya lebih suka menutup setiap file segera setelah diproses. Jadi ditempatkan tunggal file_pathdalam withpernyataan.
  • printpernyataan tidak mencetak apa pun ketika inplace=True, karena STDOUTsedang diteruskan ke file asli.
  • end=''dalam printpernyataan adalah untuk menghilangkan baris baru kosong menengah.

Dapat digunakan sebagai berikut:

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

jika Anda menghapus indentasi pada seperti di bawah ini, itu akan mencari dan mengganti dalam beberapa baris. Lihat di bawah sebagai contoh.

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)
loi
sumber
Pemformatan kode Python ini tidak terlihat benar ... (Saya mencoba memperbaikinya, tetapi tidak yakin apa yang dimaksudkan)
Andy Hayden