ค้นหาและแทนที่บรรทัดในไฟล์ใน Python


294

ฉันต้องการวนเนื้อหาของไฟล์ข้อความและทำการค้นหาและแทนที่ในบางบรรทัดและเขียนผลลัพธ์กลับไปที่ไฟล์ ฉันสามารถโหลดไฟล์ทั้งหมดในหน่วยความจำก่อนแล้วจึงเขียนกลับ แต่นั่นอาจไม่ใช่วิธีที่ดีที่สุด

เป็นวิธีที่ดีที่สุดในการทำสิ่งนี้ภายในรหัสต่อไปนี้?

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

คำตอบ:


193

ฉันคิดว่าสิ่งนี้ควรทำ โดยทั่วไปจะเขียนเนื้อหาไปยังไฟล์ใหม่และแทนที่ไฟล์เก่าด้วยไฟล์ใหม่:

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
เพียงแค่ความคิดเห็นเล็กน้อย: fileเป็นคลาสที่กำหนดไว้ล่วงหน้าของเงาที่มีชื่อเดียวกัน
ezdazuzena

4
รหัสนี้เปลี่ยนการอนุญาตในไฟล์ต้นฉบับ ฉันจะรักษาสิทธิ์ดั้งเดิมไว้ได้อย่างไร
nic

1
อะไรคือจุดของ fh คุณใช้มันในการปิดการโทร แต่ฉันไม่เห็นจุดของการสร้างไฟล์ที่จะปิดมัน ...
Wicelo

2
@Weloelo คุณต้องปิดมันเพื่อป้องกันการรั่วไหลของไฟล์ descriptor นี่คือคำอธิบายที่เหมาะสม: logilab.org/17873
Thomas Watnedal

1
ใช่ฉันค้นพบแล้วว่าmkstemp()คืน 2-tuple และ(fh, abs_path) = fh, abs_pathฉันไม่รู้ว่าเมื่อฉันถามคำถาม
Wicelo

272

ทางที่สั้นที่สุดอาจจะต้องใช้โมดูล fileinput ตัวอย่างเช่นต่อไปนี้เพิ่มหมายเลขบรรทัดในไฟล์แบบแทนที่:

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

เกิดอะไรขึ้นที่นี่คือ:

  1. ไฟล์ต้นฉบับถูกย้ายไปยังไฟล์สำรอง
  2. เอาต์พุตมาตรฐานถูกเปลี่ยนเส้นทางไปยังไฟล์ต้นฉบับภายในลูป
  3. ดังนั้นprintข้อความใด ๆ ที่เขียนลงในไฟล์ต้นฉบับ

fileinputมีเสียงระฆังและนกหวีดมากขึ้น ตัวอย่างเช่นมันสามารถใช้ในการทำงานโดยอัตโนมัติในไฟล์ทั้งหมดในsys.args[1:]โดยไม่ต้องวนซ้ำพวกเขาอย่างชัดเจน เริ่มต้นด้วย Python 3.2 นอกจากนี้ยังมีตัวจัดการบริบทที่สะดวกสำหรับใช้ในwithคำสั่ง


ในขณะที่fileinputดีเยี่ยมสำหรับสคริปต์ที่ถูกทิ้งฉันจะระวังการใช้มันในโค้ดจริงเพราะเป็นที่ยอมรับว่ามันอ่านไม่ได้หรือคุ้นเคย ในโค้ดของจริง (การผลิต) มันคุ้มค่าที่จะใช้โค้ดอีกสองสามบรรทัดเพื่อทำให้กระบวนการชัดเจนและทำให้โค้ดอ่านง่าย

มีสองตัวเลือก:

  1. ไฟล์มีขนาดไม่ใหญ่เกินไปและคุณสามารถอ่านได้ทั้งหมดในหน่วยความจำ จากนั้นปิดไฟล์เปิดใหม่อีกครั้งในโหมดการเขียนและเขียนเนื้อหาที่แก้ไขกลับมา
  2. ไฟล์มีขนาดใหญ่เกินไปที่จะเก็บไว้ในหน่วยความจำ คุณสามารถย้ายมันไปยังไฟล์ชั่วคราวและเปิดไฟล์นั้นโดยอ่านมันทีละบรรทัดเขียนกลับไปที่ไฟล์ต้นฉบับ โปรดทราบว่านี่ต้องใช้ที่เก็บข้อมูลสองเท่า

13
ฉันรู้ว่านี่มีเพียงสองบรรทัดในนั้น แต่ฉันไม่คิดว่ารหัสนั้นมีความชัดเจนมาก เพราะถ้าคุณคิดว่าแป๊บนึงถ้าคุณไม่รู้ฟังก์ชั่นมันก็มีปมน้อยมากที่เกิดขึ้น พิมพ์หมายเลขบรรทัดและสายที่ไม่ได้เป็นเช่นเดียวกับการเขียนมัน ... ถ้าคุณได้รับส่วนสำคัญของฉัน ...
chutsu

14
นี้ไมเขียนไปยังแฟ้ม มันเปลี่ยนเส้นทาง stdout ไปยังไฟล์ ดูเอกสาร
brice

32
บิตที่สำคัญที่นี่คือเครื่องหมายจุลภาคในตอนท้ายของคำสั่งพิมพ์: มันบีบอัดคำสั่งพิมพ์เพิ่มขึ้นบรรทัดใหม่อีกหนึ่ง (เป็นบรรทัดที่มีแล้ว) มันไม่ชัดเจนเลยแม้ว่า (ซึ่งเป็นเหตุผลที่ Python 3 เปลี่ยนไวยากรณ์นั้นโชคดีพอ)
VPeric

4
โปรดสังเกตว่าสิ่งนี้จะไม่ทำงานเมื่อคุณให้ตะขอเปิดสำหรับไฟล์เช่นเมื่อคุณพยายามอ่าน / เขียนไฟล์ที่เข้ารหัส UTF-16
bompf

5
สำหรับ python3print(line, end='')
Ch.Idea

80

นี่เป็นอีกตัวอย่างที่ทดสอบและจะจับคู่การค้นหาและแทนที่รูปแบบ:

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)

ตัวอย่างการใช้:

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

23
การใช้งานตัวอย่างเช่นมีการแสดงออกปกติ แต่ไม่searchExp in lineว่ามิได้line.replaceมีการดำเนินงานการแสดงออกปกติ ตัวอย่างการใช้งานผิดแน่นอน
kojiro

แทนที่จะif searchExp in line: line = line.replace(searchExp, replaceExpr)เขียนline = line.replace(searchExp, replaceExpr)ได้ ไม่มีการสร้างข้อยกเว้นบรรทัดจะยังคงไม่เปลี่ยนแปลง
David Wallace

ทำงานได้อย่างสมบูรณ์แบบสำหรับฉันเช่นกัน ฉันได้เจอจำนวนตัวอย่างอื่น ๆ ที่ดูคล้ายกับเรื่องนี้ sys.stdout.write(line)แต่เคล็ดลับคือการใช้ของ ขอบคุณอีกครั้ง!
ปราชญ์

ถ้าฉันใช้สิ่งนี้ไฟล์ของฉันจะว่างเปล่า ความคิดใด ๆ
Javier

ฉันกำลังใช้สิ่งนี้
Rakib Fiha

64

สิ่งนี้น่าจะใช้ได้: (การแก้ไขในที่)

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 นอกจากนี้หากคุณได้รับ RuntimeError: input () แอ็คทีฟอยู่แล้วให้เรียก fileinput.close ()
geographika

1
โปรดทราบว่าfilesควรจะเป็นสตริงที่มีชื่อไฟล์ไม่ได้เป็นวัตถุไฟล์
atomh33ls

9
พิมพ์เพิ่มบรรทัดใหม่ที่อาจมีอยู่แล้ว เพื่อหลีกเลี่ยงปัญหานี้ให้เพิ่ม. rstrip () เมื่อสิ้นสุดการแทนที่
Guillaume Gendre

แทนที่จะใช้ไฟล์หาเรื่องใน input () อาจเป็น fileinput.input (inplace = 1) และเรียกสคริปต์เป็น> python replace.py myfiles * .txt
chespinoza

24

ขึ้นอยู่กับคำตอบของโทมัส Watnedal อย่างไรก็ตามสิ่งนี้ไม่ได้ตอบคำถามในส่วนบรรทัดต่อบรรทัดของคำถามเดิม ฟังก์ชั่นยังสามารถแทนที่แบบ line-to-line

การนำไปใช้งานนี้จะแทนที่เนื้อหาของไฟล์โดยไม่ต้องใช้ไฟล์ชั่วคราวเนื่องจากสิทธิ์ของไฟล์ที่ตามมาจะยังคงไม่เปลี่ยนแปลง

รวมถึง re.sub แทนการแทนที่อนุญาตให้แทนที่ regex แทนการแทนที่ข้อความล้วนเท่านั้น

การอ่านไฟล์เป็นสตริงเดี่ยวแทนที่จะเป็นบรรทัดต่อบรรทัดอนุญาตให้จับคู่หลายบรรทัดและแทนที่

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
คุณอาจต้องการที่จะใช้rbและwbคุณสมบัติเมื่อเปิดไฟล์เช่นนี้จะรักษาปลายสายเดิม
Nux

ใน Python 3 คุณไม่สามารถใช้ 'wb' และ 'rb' กับ 're' มันจะให้ข้อผิดพลาด "TypeError: ไม่สามารถใช้รูปแบบสตริงในวัตถุเหมือนไบต์"

15

ตามที่ lassevk แนะนำให้เขียนไฟล์ใหม่ตามที่คุณไปนี่คือตัวอย่างโค้ด:

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

12

หากคุณต้องการฟังก์ชั่นทั่วไปที่มาแทนที่ใด ๆข้อความที่มีข้อความอื่น ๆ บางส่วนนี้อาจเป็นไปได้วิธีที่ดีที่สุดที่จะไปโดยเฉพาะอย่างยิ่งถ้าคุณเป็นแฟนของ 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

วิธี pythonic เพิ่มเติมคือการใช้ตัวจัดการบริบทเช่นโค้ดด้านล่าง:

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)

คุณสามารถค้นหาข้อมูลเต็มรูปแบบที่นี่


ในหลาม> = 3.1 คุณสามารถเปิดสองผู้จัดการบริบทในบรรทัดเดียวกัน
florisla

4

สร้างไฟล์ใหม่คัดลอกบรรทัดจากเก่าไปยังใหม่และทำการแทนที่ก่อนที่คุณจะเขียนบรรทัดไปยังไฟล์ใหม่


4

เมื่อเพิ่มคำตอบ @ Kiran ซึ่งฉันเห็นด้วยก็คือรวบรัดและ Pythonic มากกว่านี้เพิ่มตัวแปลงสัญญาณเพื่อรองรับการอ่านและการเขียนของ 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)

มันจะรักษาสิทธิ์ของไฟล์เก่าไว้ในไฟล์ใหม่หรือไม่?
Bidyut

2

ใช้คำตอบของ hamishmcn เป็นแม่แบบฉันสามารถค้นหาบรรทัดในไฟล์ที่ตรงกับ regex ของฉันและแทนที่ด้วยสตริงว่าง

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 นอก for for loop ไม่เช่นนั้นประสิทธิภาพจะลดลง
Axel

2

fileinput ค่อนข้างตรงไปตรงมาตามคำตอบก่อนหน้านี้:

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

คำอธิบาย:

  • fileinputสามารถยอมรับได้หลายไฟล์ แต่ฉันต้องการปิดแต่ละไฟล์ทันทีที่ประมวลผล วางไว้เดี่ยวดังนั้นfile_pathในwithคำสั่ง
  • printคำสั่งไม่พิมพ์อะไรเมื่อinplace=TrueเพราะSTDOUTถูกส่งต่อไปยังไฟล์ต้นฉบับ
  • end=''ในprintคำสั่งคือการกำจัดบรรทัดใหม่ที่ว่างกลาง

สามารถใช้ดังนี้:

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

0

หากคุณลบการเยื้องที่ด้านล่างนี้มันจะค้นหาและแทนที่ในหลายบรรทัด ดูตัวอย่างด้านล่าง

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)

การจัดรูปแบบของโค้ดไพ ธ อนนี้ดูไม่ถูกต้อง ... (ฉันพยายามแก้ไข แต่ไม่แน่ใจว่ามีจุดประสงค์อะไร)
Andy Hayden
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.