Python'da dosyadaki bir satırı arama ve değiştirme


294

Bir metin dosyasının içeriği üzerinde döngü ve bir arama yapmak ve bazı satırlarda değiştirmek ve sonucu dosyaya geri yazmak istiyorum. Önce tüm dosyayı belleğe yükleyebilir ve sonra geri yazabilirim, ancak muhtemelen bunu yapmanın en iyi yolu değildir.

Aşağıdaki kod içinde bunu yapmanın en iyi yolu nedir?

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

Yanıtlar:


193

Sanırım böyle bir şey yapmalı. Temel olarak içeriği yeni bir dosyaya yazar ve eski dosyayı yeni dosyayla değiştirir:

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
Sadece küçük bir yorum: fileaynı adda önceden tanımlanmış sınıfı gölgeliyor.
ezdazuzena

4
Bu kod, özgün dosyadaki izinleri değiştirir. Orijinal izinleri nasıl saklayabilirim?
nic

1
fh ne anlama geliyor, yakın çağrıda kullanın ama sadece kapatmak için bir dosya oluşturma noktasını görmüyorum ...
Wicelo

2
@Wicelo Dosya tanımlayıcısının sızmasını önlemek için kapatmanız gerekir. İşte iyi bir açıklama: logilab.org/17873
Thomas Watnedal

1
Evet mkstemp(), bunun 2 demetlik bir geri döndüğünü keşfettim (fh, abs_path) = fh, abs_pathve soruyu sorduğumda bunu bilmiyordum.
Wicelo

272

En kısa yol muhtemelen dosya girişi modülünü . Örneğin, bir dosyaya yerinde satır numaraları ekler:

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

Burada ne olur:

  1. Orijinal dosya bir yedekleme dosyasına taşınır
  2. Standart çıktı döngü içindeki orijinal dosyaya yönlendirilir
  3. Böylece herhangi bir printifade orijinal dosyaya geri yazar

fileinputdaha fazla çan ve ıslık çalıyor. Örneğin, sys.args[1:]açık bir şekilde yineleme yapmak zorunda kalmadan, içindeki tüm dosyalar üzerinde otomatik olarak çalışmak için kullanılabilir . Python 3.2'den başlayarak, bir withifadede kullanım için uygun bir içerik yöneticisi de sağlar .


Süre fileinputGerileme komut dosyaları için harika da, gerçek kodda kullanmaktan kaçınırım, çünkü kuşkusuz çok okunabilir veya tanıdık değil. Gerçek (üretim) kodda, süreci açık hale getirmek ve böylece kodu okunabilir hale getirmek için sadece birkaç satır daha harcamak faydalı olacaktır.

İki seçenek vardır:

  1. Dosya çok büyük değil ve sadece belleğe tamamen okuyabilirsiniz. Sonra dosyayı kapatın, yazma modunda yeniden açın ve değiştirilen içeriği geri yazın.
  2. Dosya bellekte depolanmak için çok büyük; dosyayı geçici bir dosyaya taşıyabilir ve satır satır okuyarak orijinal dosyaya yazarak açabilirsiniz. Bunun iki kez depolanması gerektiğini unutmayın.

13
Bu sadece iki satır olduğunu biliyorum, ancak kod kendi içinde çok etkileyici olduğunu sanmıyorum. Çünkü bir saniye düşünürseniz, işlevi bilmiyorsanız, olup bitenler hakkında çok az ipucu vardır. Satır numarasını ve satırı yazdırmak, yazmakla aynı şey değil ... eğer
özlemimi alırsanız

14
Bu YAPAR dosyaya yazma. Stdout'u dosyaya yönlendirir. Belgelere
brice

32
Buradaki anahtar bit, print ifadesinin sonundaki virgüldür: başka bir yeni satır ekleyerek print satırına basar (satırda zaten bir tane var gibi). Yine de çok açık değil (bu yüzden Python 3 bu sözdizimini neyse ki değiştirdi).
VPeric

4
Dosyaya bir açılış kancası sağladığınızda, örneğin UTF-16 kodlu dosyaları okumaya / yazmaya çalıştığınızda bunun işe yaramadığını lütfen unutmayın.
bompf

5
Python3 içinprint(line, end='')
Ch.Idea

80

Test edilen ve arama ve değiştirme desenleriyle eşleşecek başka bir örnek:

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)

Örnek kullanım:

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

23
Örnek kullanım, normal bir ifade sağlar, ancak normal ifade işlemleri de searchExp in linedeğildir line.replace. Elbette örnek kullanım yanlıştır.
kojiro

Bunun yerine if searchExp in line: line = line.replace(searchExp, replaceExpr)sadece yazabilirsiniz line = line.replace(searchExp, replaceExpr). İstisna oluşturulmaz, çizgi değişmeden kalır.
David Wallace

Benim için de mükemmel çalıştı. Buna çok benzeyen bir dizi başka örnekle karşılaştım, ama püf noktası sys.stdout.write(line). Tekrar teşekkürler!
Adaçayı

Bunu kullanırsam dosyam boşalır. Herhangi bir fikir?
Javier López Tomás

Bunu kullanıyorum
Rakib Fiha

64

Bunun çalışması gerekir: (yerinde düzenleme)

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. Bir RuntimeError almak Ayrıca eğer: girdi () zaten aktif ardından (fileinput.close call)
Geographika

1
Bunun bir dosya nesnesi değilfiles , dosya adını içeren bir dize olması gerektiğini unutmayın .
atomh33ls

9
print zaten orada olabilecek yeni bir satır ekler. Bundan kaçınmak için, değiştirmelerinizin sonuna .rstrip () ekleyin
Guillaume Gendre

Bunun yerine input () dosyasında arg dosyalarını kullan, fileinput.input (inplace = 1) olabilir ve komut dosyasını> python
replace.py

24

Thomas Watnedal'ın cevabına dayanarak. Ancak, bu orijinal sorunun satırdan satıra kısmını tam olarak cevaplamaz. Fonksiyon satırdan hatta değişebilir

Bu uygulama, dosya içeriğini geçici dosyalar kullanmadan değiştirir, bunun sonucunda dosya izinleri değişmez.

Ayrıca replace yerine re.sub, yalnızca düz metin yerine regex değiştirmeye izin verir.

Dosyayı satır satır yerine tek bir dize olarak okumak, çok satırlı eşleşmeye ve değiştirmeye olanak tanır.

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
Orijinal satır sonlarını koruyacağından dosyaları açarken kullanmak rbve wböznitelikleri kullanmak isteyebilirsiniz
Nux

Python 3'te, 're' ile 'wb' ve 'rb' kullanamazsınız. "TypeError: bayt benzeri bir nesnede dize deseni kullanamaz" hatası verir

15

Lassevk'in önerdiği gibi, yeni dosyayı gittiğiniz gibi yazın, işte bazı örnek kodlar:

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

12

Herhangi bir metni başka bir metinle değiştiren genel bir işlev istiyorsanız, özellikle regex'lerin hayranıysanız, bu muhtemelen en iyi yoldur:

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

Daha pitonik bir yol, aşağıdaki kod gibi bağlam yöneticilerini kullanmak olacaktır:

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)

Snippet'in tamamını burada bulabilirsiniz .


Python> = 3.1'de iki bağlam yöneticisini aynı satırda açabilirsiniz .
florisla

4

Yeni bir dosya oluşturun, satırları eskiden yeniye kopyalayın ve satırları yeni dosyaya yazmadan önce değiştirin.


4

@ Kiran'ın daha özlü ve Pythonic olduğunu kabul ettiğim cevabını genişleterek, UTF-8'in okunmasını ve yazılmasını desteklemek için kodekler ekliyor:

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)

Yeni dosyadaki eski dosyanın iznini koruyacak mı?
Bidyut

2

Hamishmcn'ın cevabını şablon olarak kullanarak, bir dosyada normal ifademle eşleşen bir satırı arayabildim ve boş dize ile değiştirebildim.

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
Regex'i for döngüsü DIŞINDA derlemelisiniz, aksi takdirde performans kaybıdır
Axel

2

fileinput önceki yanıtlarda belirtildiği gibi oldukça basittir:

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

Açıklama:

  • fileinputbirden fazla dosyayı kabul edebilir, ancak işlendikten hemen sonra her bir dosyayı kapatmayı tercih ederim. Yani açıklamada tek file_pathyer with.
  • printifadesi ne zaman şey yazmaz inplace=Trueçünkü STDOUTorijinal dosyaya yönlendiriliyor.
  • end=''içinde printdeyimi ara boş yeni satırlar ortadan kaldırmaktır.

Aşağıdaki gibi kullanılabilir:

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

0

girintiyi aşağıdaki gibi kaldırırsanız, birden çok satırda arama yapar ve değiştirir. Örnek için aşağıya bakın.

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)

Bu Python kodunun biçimlendirilmesi pek doğru görünmüyor ... (Düzeltmeye çalıştım, ancak neyin amaçlandığından emin değildim)
Andy Hayden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.