Tìm kiếm và thay thế một dòng trong một tệp bằng Python


292

Tôi muốn lặp lại nội dung của tệp văn bản và thực hiện tìm kiếm và thay thế trên một số dòng và viết kết quả trở lại tệp. Trước tiên tôi có thể tải toàn bộ tệp trong bộ nhớ và sau đó ghi lại, nhưng đó có lẽ không phải là cách tốt nhất để làm điều đó.

Cách tốt nhất để làm điều này, trong đoạn mã sau đây là gì?

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

Câu trả lời:


191

Tôi đoán một cái gì đó như thế này nên làm điều đó. Về cơ bản, nó ghi nội dung vào một tệp mới và thay thế tệp cũ bằng tệp mới:

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
Chỉ là một nhận xét nhỏ: fileđang phủ bóng lớp được xác định trước cùng tên.
ezdazuzena

4
Mã này thay đổi các quyền trên tệp gốc. Làm thế nào tôi có thể giữ quyền ban đầu?
nic

1
Điểm của fh là gì, bạn sử dụng nó trong cuộc gọi gần nhưng tôi không thấy điểm tạo tệp chỉ để đóng nó ...
Wicelo

2
@Wicelo Bạn cần đóng nó để tránh rò rỉ bộ mô tả tập tin. Đây là một lời giải thích hợp lý: logilab.org/17873
Thomas Watnedal

1
Có, tôi đã phát hiện ra rằng mkstemp()sẽ trả lại 2 tuple và (fh, abs_path) = fh, abs_path, tôi không biết rằng khi tôi đặt câu hỏi.
Wicelo

271

Cách ngắn nhất có lẽ là sử dụng mô-đun fileinput . Ví dụ: phần sau đây thêm số dòng vào một tệp, tại chỗ:

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

Điều gì xảy ra ở đây là:

  1. Các tập tin ban đầu được chuyển đến một tập tin sao lưu
  2. Đầu ra tiêu chuẩn được chuyển hướng đến tệp gốc trong vòng lặp
  3. Do đó, bất kỳ printcâu lệnh nào ghi lại vào tập tin gốc

fileinputcó nhiều chuông và còi. Ví dụ, nó có thể được sử dụng để tự động hoạt động trên tất cả các tệp trong sys.args[1:]mà không cần bạn phải lặp lại một cách rõ ràng. Bắt đầu với Python 3.2, nó cũng cung cấp một trình quản lý bối cảnh thuận tiện để sử dụng trong một withcâu lệnh.


Mặc dù fileinputlà tuyệt vời cho các kịch bản bỏ đi, tôi sẽ cảnh giác khi sử dụng nó trong mã thực bởi vì phải thừa nhận rằng nó không dễ đọc hoặc quen thuộc. Trong mã thực tế (sản xuất), đáng để chi tiêu thêm một vài dòng mã để làm cho quy trình rõ ràng và do đó làm cho mã có thể đọc được.

Có hai lựa chọn:

  1. Tệp không quá lớn và bạn chỉ có thể đọc toàn bộ vào bộ nhớ. Sau đó đóng tệp, mở lại ở chế độ ghi và ghi lại nội dung đã sửa đổi.
  2. Các tập tin quá lớn để được lưu trữ trong bộ nhớ; bạn có thể chuyển nó sang một tập tin tạm thời và mở nó ra, đọc từng dòng một, viết lại vào tập tin gốc. Lưu ý rằng điều này đòi hỏi gấp đôi dung lượng lưu trữ.

13
Tôi biết điều này chỉ có hai dòng trong đó, tuy nhiên tôi không nghĩ rằng bản thân mã này rất biểu cảm. Bởi vì nếu bạn suy nghĩ một chút, nếu bạn không biết chức năng này, có rất ít manh mối trong những gì đang diễn ra. In số dòng và dòng không giống như viết nó ... nếu bạn có ý chính của tôi ...
chutsu

14
Đây NÀO ghi vào tập tin. Nó chuyển hướng stdout vào tập tin. Hãy xem tài liệu
brice

32
Bit chính ở đây là dấu phẩy ở cuối câu lệnh in: nó thay thế câu lệnh in thêm một dòng mới (vì dòng đã có một). Tuy nhiên, điều đó không rõ ràng lắm (đó là lý do Python 3 thay đổi cú pháp đó, thật may là đủ).
VPeric

4
Xin lưu ý rằng điều này không hoạt động khi bạn cung cấp một móc mở vào tệp, ví dụ như khi bạn cố đọc / ghi các tệp được mã hóa UTF-16.
bompf

5
Đối với python3,print(line, end='')
Ch.Idea

80

Đây là một ví dụ khác đã được thử nghiệm và sẽ khớp với các mẫu tìm kiếm & thay thế:

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)

Ví dụ sử dụng:

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

23
Việc sử dụng ví dụ cung cấp một biểu thức chính quy, nhưng cũng không searchExp in linephải line.replacelà các hoạt động biểu thức chính quy. Chắc chắn việc sử dụng ví dụ là sai.
kojiro

Thay vì if searchExp in line: line = line.replace(searchExp, replaceExpr)bạn chỉ có thể viết line = line.replace(searchExp, replaceExpr). Không có ngoại lệ được tạo ra, dòng chỉ không thay đổi.
David Wallace

Làm việc hoàn hảo cho tôi là tốt. Tôi đã bắt gặp một số ví dụ khác trông rất giống với điều này, nhưng mẹo là sử dụng sys.stdout.write(line). Cảm ơn một lần nữa!
Hiền nhân

Nếu tôi sử dụng điều này, tập tin của tôi sẽ trống. Bất kỳ ý tưởng?
Javier López Tomás

Tôi đang sử dụng cái này
Rakib Fiha

64

Điều này sẽ làm việc: (chỉnh sửa tại chỗ)

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. Ngoài ra nếu bạn nhận được RuntimeError: input () đã hoạt động thì hãy gọi fileinput.close ()
geographika

1
Lưu ý rằng filesphải là một chuỗi chứa tên tệp, không phải là một đối tượng tệp .
Atomh33ls

9
in thêm một dòng mới có thể đã ở đó. để tránh điều này, hãy thêm .rstrip () vào cuối phần thay thế của bạn
Guillaume Gendre

Thay vì sử dụng các tệp arg trong input (), nó có thể là fileinput.input (inplace = 1) và gọi tập lệnh là> python thay thế myfiles * .txt
chespinoza

24

Dựa trên câu trả lời của Thomas Watnedal. Tuy nhiên, điều này không trả lời chính xác phần trực tuyến của câu hỏi ban đầu. Hàm vẫn có thể thay thế trên cơ sở từng dòng

Việc triển khai này thay thế nội dung tệp mà không sử dụng các tệp tạm thời, do đó, quyền của tệp không thay đổi.

Ngoài ra re.sub thay vì thay thế, cho phép thay thế regex thay vì chỉ thay thế văn bản thuần túy.

Đọc tệp dưới dạng một chuỗi thay vì từng dòng cho phép khớp và thay thế nhiều dòng.

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
Bạn có thể muốn sử dụng rbwbcác thuộc tính khi mở tệp vì điều này sẽ duy trì các kết thúc dòng gốc
Nux

Trong Python 3, bạn không thể sử dụng 'wb' và 'rb' với 're'. Nó sẽ báo lỗi "TypeError: không thể sử dụng mẫu chuỗi trên một đối tượng giống byte"

15

Như lassevk gợi ý, hãy viết ra tệp mới khi bạn đi, đây là một số ví dụ mã:

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

12

Nếu bạn muốn một chức năng chung thay thế bất kỳ văn bản nào bằng một số văn bản khác, đây có thể là cách tốt nhất để đi, đặc biệt nếu bạn là người hâm mộ của 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

Một cách pythonic hơn sẽ là sử dụng các trình quản lý bối cảnh như mã dưới đây:

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)

Bạn có thể tìm thấy đoạn trích đầy đủ ở đây .


Trong Python> = 3.1, bạn có thể mở hai trình quản lý bối cảnh trên cùng một dòng .
florisla

4

Tạo một tệp mới, sao chép các dòng từ cũ sang mới và thực hiện thay thế trước khi bạn viết các dòng vào tệp mới.


4

Mở rộng câu trả lời của @ Kiran, mà tôi đồng ý là ngắn gọn và Pythonic hơn, điều này bổ sung các codec để hỗ trợ việc đọc và viết 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)

Nó sẽ bảo vệ sự cho phép của tập tin cũ trong tập tin mới?
Bidyut

2

Sử dụng câu trả lời của hamishmcn làm mẫu, tôi có thể tìm kiếm một dòng trong một tệp khớp với biểu thức chính quy của tôi và thay thế nó bằng chuỗi trống.

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
Bạn nên biên dịch regex BÊN NGOÀI vòng lặp for, nếu không thì lãng phí hiệu năng
Axel

2

fileinput khá đơn giản như đã đề cập trong các câu trả lời trước:

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

Giải trình:

  • fileinputcó thể chấp nhận nhiều tệp, nhưng tôi thích đóng từng tệp ngay khi nó được xử lý. Vì vậy, đặt đơn file_pathtrong withtuyên bố.
  • printcâu lệnh không in bất cứ điều gì khi inplace=True, bởi vì STDOUTđang được chuyển tiếp đến tập tin gốc.
  • end=''trong printtuyên bố là để loại bỏ các dòng mới trống trung gian.

Có thể được sử dụng như sau:

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

0

nếu bạn loại bỏ thụt lề như bên dưới, nó sẽ tìm kiếm và thay thế trong nhiều dòng. Xem dưới đây ví dụ.

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)

Định dạng của mã Python này trông không đúng lắm ... (Tôi đã cố sửa, nhưng không chắc là nó có ý định gì)
Andy Hayden
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.