Làm thế nào để sửa đổi một tập tin văn bản?


175

Tôi đang sử dụng Python và muốn chèn một chuỗi vào tệp văn bản mà không xóa hoặc sao chép tệp. Làm thế nào tôi có thể làm điều đó?


1
Bạn có thể tham khảo câu trả lời này của Alex Martelli.
Alok



@Ani bài đăng khác một bản sao của Chèn dòng tại vị trí được chỉ định của tệp văn bản và chắc chắn có câu trả lời rõ ràng sáng tác ở đây, Tại sao không thêm câu trả lời của bạn ở đây thay vì cách khác? Câu trả lời được chấp nhận không phải là một yêu cầu cho một câu hỏi hay.
Bhargav Rao

@BhargavRao Bầu chọn rút lại. Tôi nên tìm thấy bản sao đó mặc dù!
Ani Menon

Câu trả lời:


134

Thật không may, không có cách nào để chèn vào giữa một tập tin mà không viết lại nó. Như các áp phích trước đã chỉ ra, bạn có thể thêm vào một tệp hoặc ghi đè lên một phần của tệp bằng cách sử dụng tìm kiếm nhưng nếu bạn muốn thêm nội dung ở đầu hoặc giữa, bạn sẽ phải viết lại.

Đây là một điều hệ điều hành, không phải là một điều Python. Nó là như nhau trong tất cả các ngôn ngữ.

Những gì tôi thường làm là đọc từ tệp, thực hiện các sửa đổi và ghi nó ra một tệp mới gọi là myfile.txt.tmp hoặc đại loại như thế. Điều này tốt hơn là đọc toàn bộ tệp vào bộ nhớ vì tệp có thể quá lớn cho điều đó. Khi tập tin tạm thời hoàn thành, tôi đổi tên nó giống như tập tin gốc.

Đây là một cách tốt, an toàn để làm điều đó bởi vì nếu tệp viết bị treo hoặc hủy bỏ vì bất kỳ lý do gì, bạn vẫn có tệp gốc chưa được xử lý.


3
Các công cụ unix như awk / sed có làm điều gì đó tương tự trong mã của họ không?
Manish Gill

Điều này không đúng trong tất cả các ngôn ngữ. Trong ActionScript: fileStream.openAsync (tên tệp, FileMode.UPDATE); Sau đó, tôi có thể đi bất cứ nơi nào trong tập tin tôi muốn và thay đổi bất cứ điều gì.
AndrewBenjamin

2
@AndrewBenjamin Bạn có biết hệ thống gọi ActionScript đang làm gì không? Có khả năng openAsync đọc tệp và ghi tệp mới sau cuộc gọi không?
AlexLordThorsen

@Rawrgulmuffins Tôi không. Tuy nhiên, tôi biết rằng nó không đọc toàn bộ tệp vào bộ nhớ, vì tôi đã sử dụng nó để xử lý các tệp có dung lượng vài GB. Tôi nghi ngờ nó giống như viết với streamer C #. Tôi xem python như một công cụ để thực hiện những việc nhỏ một cách nhanh chóng, thay vì phát triển quy mô lớn và thao tác tập tin.
AndrewBenjamin

4
@AndrewBenjamin, người dùng không hỏi về việc tìm kiếm xung quanh tệp và thay đổi nó (mọi ngôn ngữ tôi biết đều có thể làm điều đó); anh ta đang hỏi về việc chèn văn bản, khác với việc thay đổi / ghi đè những gì đã có trong tệp. Có thể trong ứng dụng thực tế thì khác, nhưng không có gì tôi có thể tìm thấy trong API ActionScript chỉ ra rằng nó hoạt động khác với bất kỳ ngôn ngữ nào khác trong vấn đề này.
eestrada

104

Phụ thuộc vào những gì bạn muốn làm. Để nối thêm, bạn có thể mở nó bằng "a":

 with open("foo.txt", "a") as f:
     f.write("new line\n")

Nếu bạn muốn trả trước một cái gì đó bạn phải đọc từ tệp trước:

with open("foo.txt", "r+") as f:
     old = f.read() # read everything in the file
     f.seek(0) # rewind
     f.write("new line\n" + old) # write the new line before

9
Chỉ cần một bổ sung nhỏ, để sử dụng withcâu lệnh trong Python 2.5, bạn cần thêm "từ lần nhập tương lai with_statement". Ngoài ra, việc mở các tệp bằng withcâu lệnh chắc chắn dễ đọc hơn và ít bị lỗi hơn so với đóng thủ công.
Alexander Kojevnikov

2
Bạn có thể xem xét fileinputlib của người trợ giúp với việc xử lý thói quen mở / đọc / sửa đổi / ghi / thay thế một cách độc đáo khi sử dụng inline=Truearg. Ví dụ ở đây: stackoverflow.com/a/2363893/47390
mikegreenberg

3
Chỉ cần đừng quên đóng tập tin. f.Close()
D.Rosado

5
Đó không phải là phong cách tôi sử dụng, D.Rosado, nhưng khi sử dụng với phong cách, tôi không nghĩ bạn cần phải đóng thủ công. Việc theo dõi tài nguyên mà nó tạo ra.
Chris

4
Bạn không cần phải tự đóng tệp. Đó là toàn bộ quan điểm của việc sử dụng "với" ở đây. (Chà, thực ra, Python thực hiện điều này ngay khi đối tượng tệp là rác được thu thập, điều này trong CPython xảy ra khi tên bị ràng buộc ngoài phạm vi ... nhưng các triển khai khác thì không, và CPython có thể ngừng thực hiện một ngày nào đó , vì vậy "với" được khuyến nghị)
Jürgen A. Erhard

71

Các fileinputmô-đun của thư viện chuẩn của Python sẽ viết lại một inplace tập tin nếu bạn sử dụng inplace tham số = 1:

import sys
import fileinput

# replace all occurrences of 'sit' with 'SIT' and insert a line after the 5th
for i, line in enumerate(fileinput.input('lorem_ipsum.txt', inplace=1)):
    sys.stdout.write(line.replace('sit', 'SIT'))  # replace 'sit' and write
    if i == 4: sys.stdout.write('\n')  # write a blank line after the 5th line

1
Làm thế nào điều này được mong đợi để làm việc trong python3? Tôi vừa chuyển một ứng dụng có một số mã như thế này từ python sang python3 và tôi hoàn toàn không thể làm cho nó hoạt động được. Biến 'line' là một loại byte, tôi đã thử giải mã nó thành unicode và sau đó sửa đổi nó và sau đó mã hóa lại thành byte nhưng nó không hoạt động đúng. Nó đưa ra một số ngoại lệ tôi không thể nhớ ra khỏi đỉnh đầu của tôi. Có phải mọi người đang sử dụng fileinput inplace = 1 trong python3 có thành công nào không?
robru 21/2/2015

1
@Robru: đây là mã Python 3
jfs

13
Nhưng không có vấn đề gì vì bạn đã thử nó trước trên một tệp không quan trọng phải không?
Paula Livingstone

33

Viết lại một tập tin tại chỗ thường được thực hiện bằng cách lưu bản sao cũ với một tên đã sửa đổi. Unix folks thêm một ~để đánh dấu cái cũ. Windows folks làm tất cả mọi thứ - thêm .bak hoặc .old - hoặc đổi tên hoàn toàn tệp hoặc đặt ~ ở phía trước tên.

import shutil
shutil.move( afile, afile+"~" )

destination= open( aFile, "w" )
source= open( aFile+"~", "r" )
for line in source:
    destination.write( line )
    if <some condition>:
        destination.write( >some additional line> + "\n" )
source.close()
destination.close()

Thay vì shutil, bạn có thể sử dụng như sau.

import os
os.rename( aFile, aFile+"~" )

1
Có vẻ tốt. Tự hỏi nếu .readlines () tốt hơn lặp lại nguồn?
bozdoz

2
@bozdoz: lặp lại là tốt hơn vì các dòng đọc đọc toàn bộ tập tin. Không tốt cho các tập tin lớn. Tất nhiên, điều này giả định rằng bạn có thể thực hiện các sửa đổi của mình theo cách địa phương hóa như vậy. Đôi khi bạn không thể, hoặc mã của bạn phức tạp hơn nhiều.
Jürgen A. Erhard

@ S.Lott: os.rename(aFile, aFile + "~")sẽ sửa đổi tên của tệp nguồn, không tạo bản sao.
Patapoom

14

Mô-đun mmap của Python sẽ cho phép bạn chèn vào một tệp. Mẫu sau đây cho thấy cách nó có thể được thực hiện trong Unix (Windows mmap có thể khác). Lưu ý rằng điều này không xử lý tất cả các điều kiện lỗi và bạn có thể bị hỏng hoặc mất tệp gốc. Ngoài ra, điều này sẽ không xử lý chuỗi unicode.

import os
from mmap import mmap

def insert(filename, str, pos):
    if len(str) < 1:
        # nothing to insert
        return

    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(filename))
    origSize = m.size()

    # or this could be an error
    if pos > origSize:
        pos = origSize
    elif pos < 0:
        pos = 0

    m.resize(origSize + len(str))
    m[pos+len(str):] = m[pos:origSize]
    m[pos:pos+len(str)] = str
    m.close()
    f.close()

Cũng có thể thực hiện việc này mà không cần mmap với các tệp được mở ở chế độ 'r +', nhưng sẽ không thuận tiện và kém hiệu quả hơn khi bạn phải đọc và lưu tạm thời nội dung của tệp từ vị trí chèn vào EOF - có thể là rất lớn.


14

Như Adam đã đề cập, bạn phải xem xét các giới hạn hệ thống của mình trước khi quyết định tiếp cận xem bạn có đủ bộ nhớ để đọc tất cả vào bộ nhớ thay thế các phần của nó và viết lại không.

Nếu bạn đang xử lý một tệp nhỏ hoặc không có vấn đề về bộ nhớ, điều này có thể giúp:

Tùy chọn 1) Đọc toàn bộ tệp vào bộ nhớ, thực hiện thay thế regex trên toàn bộ hoặc một phần của dòng và thay thế nó bằng dòng đó cộng với dòng bổ sung. Bạn sẽ cần đảm bảo rằng 'đường giữa' là duy nhất trong tệp hoặc nếu bạn có dấu thời gian trên mỗi dòng thì điều này sẽ rất đáng tin cậy.

# open file with r+b (allow write and binary mode)
f = open("file.log", 'r+b')   
# read entire content of file into memory
f_content = f.read()
# basically match middle line and replace it with itself and the extra line
f_content = re.sub(r'(middle line)', r'\1\nnew line', f_content)
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(f_content)
# close file
f.close()

Tùy chọn 2) Tìm ra đường giữa và thay thế nó bằng đường đó cộng với đường phụ.

# open file with r+b (allow write and binary mode)
f = open("file.log" , 'r+b')   
# get array of lines
f_content = f.readlines()
# get middle line
middle_line = len(f_content)/2
# overwrite middle line
f_content[middle_line] += "\nnew line"
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(''.join(f_content))
# close file
f.close()

2

Đã viết một lớp nhỏ để làm điều này sạch sẽ.

import tempfile

class FileModifierError(Exception):
    pass

class FileModifier(object):

    def __init__(self, fname):
        self.__write_dict = {}
        self.__filename = fname
        self.__tempfile = tempfile.TemporaryFile()
        with open(fname, 'rb') as fp:
            for line in fp:
                self.__tempfile.write(line)
        self.__tempfile.seek(0)

    def write(self, s, line_number = 'END'):
        if line_number != 'END' and not isinstance(line_number, (int, float)):
            raise FileModifierError("Line number %s is not a valid number" % line_number)
        try:
            self.__write_dict[line_number].append(s)
        except KeyError:
            self.__write_dict[line_number] = [s]

    def writeline(self, s, line_number = 'END'):
        self.write('%s\n' % s, line_number)

    def writelines(self, s, line_number = 'END'):
        for ln in s:
            self.writeline(s, line_number)

    def __popline(self, index, fp):
        try:
            ilines = self.__write_dict.pop(index)
            for line in ilines:
                fp.write(line)
        except KeyError:
            pass

    def close(self):
        self.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        with open(self.__filename,'w') as fp:
            for index, line in enumerate(self.__tempfile.readlines()):
                self.__popline(index, fp)
                fp.write(line)
            for index in sorted(self.__write_dict):
                for line in self.__write_dict[index]:
                    fp.write(line)
        self.__tempfile.close()

Sau đó, bạn có thể sử dụng nó theo cách này:

with FileModifier(filename) as fp:
    fp.writeline("String 1", 0)
    fp.writeline("String 2", 20)
    fp.writeline("String 3")  # To write at the end of the file

Điều này không làm việc cho cá nhân tôi, nó thêm văn bản vào tập tin nhưng nó xóa mọi thứ trước!
Bret Hawker

Thật vậy, điều này hoàn toàn không hoạt động. Xấu hổ, vì dường như đó là một ý tưởng tốt.
Mario Krušelj

0

Nếu bạn biết một số unix, bạn có thể thử như sau:

Ghi chú: $ có nghĩa là dấu nhắc lệnh

Giả sử bạn có tệp my_data.txt với nội dung như vậy:

$ cat my_data.txt
This is a data file
with all of my data in it.

Sau đó, sử dụng osmô-đun, bạn có thể sử dụng các sedlệnh thông thường

import os

# Identifiers used are:
my_data_file = "my_data.txt"
command = "sed -i 's/all/none/' my_data.txt"

# Execute the command
os.system(command)

Nếu bạn không biết về sed, hãy kiểm tra nó, nó cực kỳ hữu ích.


3
Nó hoàn toàn không phải là Pythonic
DarkSuniuM
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.