จะแก้ไขไฟล์ข้อความได้อย่างไร?


175

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


1
คุณสามารถอ้างถึงคำตอบนี้โดย Alex Martelli
Alok



@Ani โพสต์อื่น ๆเป็นซ้ำของการใส่สายที่ระบุตำแหน่งของไฟล์ข้อความต่อไปและแน่นอนมีคำตอบที่ชัดเจนประกอบที่นี่ทำไมไม่เพิ่มคำตอบของคุณที่นี่แทนวิธีอื่น ๆ ? คำตอบที่ยอมรับไม่ใช่ข้อกำหนดสำหรับคำถามที่ดี
Bhargav Rao

@BhargavRao โหวตหดกลับ ฉันควรจะพบว่าซ้ำกัน!
Ani Menon

คำตอบ:


134

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

นี่คือสิ่งที่ระบบปฏิบัติการไม่ใช่สิ่งที่หลาม มันเหมือนกันในทุกภาษา

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

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


3
เครื่องมือ unix เช่น awk / sed ทำสิ่งที่คล้ายกันในรหัสของพวกเขาหรือไม่?
Manish Gill

ไม่เป็นความจริงเลยว่านี่จะเหมือนกันในทุกภาษา ใน ActionScript: fileStream.openAsync (ชื่อไฟล์, FileMode.UPDATE); จากนั้นฉันสามารถไปที่ใดก็ได้ในไฟล์ที่ฉันต้องการและเปลี่ยนแปลงอะไรก็ได้
AndrewBenjamin

2
@AndrewBenjamin คุณรู้หรือไม่ว่าระบบเรียกว่า ActionScript กำลังทำอะไร มีความเป็นไปได้ที่ openAsync จะอ่านไฟล์และเขียนไฟล์ใหม่หลังจากการโทรหรือไม่?
AlexLordThorsen

@Rawrgulmuffins ฉันทำไม่ได้ อย่างไรก็ตามฉันรู้ว่ามันไม่ได้อ่านไฟล์ทั้งหมดในหน่วยความจำเพราะฉันใช้มันเพื่อจัดการกับขนาดไฟล์หลาย GB ฉันสงสัยว่ามันเหมือนกับการเขียนด้วย C # streamwriter ฉันดูหลามเป็นเครื่องมือสำหรับทำสิ่งเล็ก ๆ ได้อย่างรวดเร็วมากกว่าการพัฒนาขนาดใหญ่และการจัดการไฟล์
AndrewBenjamin

4
@AndrewBenjamin ผู้ใช้ไม่ได้ถามถึงการค้นหาไฟล์และเปลี่ยนมัน (ทุกภาษาที่ฉันรู้สามารถทำได้); เขาถามเกี่ยวกับการแทรกข้อความซึ่งแตกต่างจากการเปลี่ยน / เขียนทับสิ่งที่มีอยู่ในไฟล์อยู่แล้ว บางทีในแอปพลิเคชั่นที่ใช้งานจริงมันแตกต่างกัน แต่ไม่มีอะไรที่ฉันสามารถหาได้ในActionScript APIบ่งชี้ว่ามันทำงานแตกต่างจากภาษาอื่น ๆ ในเรื่องนี้
eestrada

104

ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำ ในการผนวกคุณสามารถเปิดด้วย "a":

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

หากคุณต้องการ preprend สิ่งที่คุณต้องอ่านจากไฟล์ก่อน:

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
เพียงเล็กน้อยนอกจากนี้ในการใช้withคำสั่งใน Python 2.5 คุณจำเป็นต้องเพิ่ม "จากการนำเข้าในอนาคต with_statement" นอกจากนั้นการเปิดไฟล์ที่มีwithคำสั่งนั้นสามารถอ่านได้ง่ายกว่าและมีข้อผิดพลาดน้อยกว่าการปิดแมนนวล
Alexander Kojevnikov

2
คุณอาจพิจารณาตัวfileinputช่วย lib พร้อมกับจัดการรูทีน open / read / modified / write / replace สกปรกอย่างดีเมื่อใช้inline=TrueARG ตัวอย่างที่นี่: stackoverflow.com/a/2363893/47390
mikegreenberg

3
อย่าลืมปิดไฟล์ f.Close()
D.Rosado

5
มันไม่ใช่สไตล์ที่ฉันใช้ D.Rosado แต่เมื่อใช้กับสไตล์ฉันไม่คิดว่าคุณต้องปิดตัวเอง พร้อมกับติดตามทรัพยากรที่สร้างขึ้น
คริส

4
คุณไม่จำเป็นต้องปิดไฟล์ด้วยตนเอง นั่นเป็นจุดรวมของการใช้ "กับ" ที่นี่ (จริง ๆ แล้ว Python ทำสิ่งนี้ทันทีที่วัตถุไฟล์รวบรวมขยะซึ่งใน CPython เกิดขึ้นเมื่อชื่อที่ผูกไว้กับมันอยู่นอกขอบเขต ... แต่การใช้งานอื่นไม่ได้ดำเนินการและ CPython อาจหยุดทำบางวัน ดังนั้นขอแนะนำ "กับ")
Jürgen A. Erhard

71

fileinputโมดูลของห้องสมุดมาตรฐานหลามจะเขียนซ้ำ inplace ไฟล์ถ้าคุณใช้ inplace = 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
สิ่งนี้คาดว่าจะทำงานใน python3 ได้อย่างไร ฉันเพิ่งย้ายแอพที่มีรหัสเช่นนี้จาก python ถึง python3 และฉันไม่สามารถทำให้มันใช้งานได้เลย ตัวแปร 'line' เป็นประเภท bytes ฉันพยายามถอดรหัสมันเป็น unicode แล้วทำการแก้ไขและเข้ารหัสกลับเป็น bytes แต่มันจะไม่ทำงาน มันยกข้อยกเว้นบางอย่างที่ฉันจำไม่ได้จากส่วนบนของหัว ผู้ใช้งาน fileinput inplace = 1 ใน python3 มีความสำเร็จหรือไม่?
robru

1
@Robru: นี่คือรหัส Python 3 ของ
jfs

13
แต่มันไม่มีปัญหาเพราะคุณทดสอบมันลงบนไฟล์ที่ไม่สำคัญใช่ไหม?
พอลล่าลิฟวิงสโตน

33

การเขียนไฟล์ซ้ำมักจะทำโดยการบันทึกสำเนาเก่าด้วยชื่อที่แก้ไข คน Unix เพิ่ม a ~เพื่อทำเครื่องหมายเก่า ผู้ใช้ Windows ทำทุกสิ่ง - เพิ่ม. bak หรือ. old - หรือเปลี่ยนชื่อไฟล์ทั้งหมดหรือใส่เครื่องหมาย ~ ที่ด้านหน้าของชื่อ

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()

แทนที่จะshutilใช้คุณสามารถใช้สิ่งต่อไปนี้

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

1
ดูดี. หากสงสัยว่า .readlines () ดีกว่าการทำซ้ำแหล่งข้อมูลหรือไม่
bozdoz

2
@bozdoz: การวนซ้ำดีกว่าเนื่องจาก readlines อ่านไฟล์ทั้งหมด ไม่ดีสำหรับไฟล์ขนาดใหญ่ แน่นอนว่าคุณสามารถทำการแก้ไขด้วยวิธีการแปล บางครั้งคุณทำไม่ได้หรือรหัสของคุณซับซ้อนขึ้นมาก
Jürgen A. Erhard

@ S.Lott: os.rename(aFile, aFile + "~")จะแก้ไขชื่อของไฟล์ต้นฉบับไม่ใช่การสร้างสำเนา
Patapoom

14

โมดูล mmap ของ Python จะอนุญาตให้คุณแทรกลงในไฟล์ ตัวอย่างต่อไปนี้แสดงวิธีการที่สามารถทำได้ใน Unix (Windows mmap อาจแตกต่างกัน) โปรดทราบว่าสิ่งนี้ไม่ได้จัดการกับเงื่อนไขข้อผิดพลาดทั้งหมดและคุณอาจเสียหายหรือสูญเสียไฟล์ต้นฉบับ นอกจากนี้สิ่งนี้จะไม่จัดการกับสตริง 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()

นอกจากนี้ยังเป็นไปได้ที่จะทำเช่นนี้โดยไม่ต้อง mmap กับไฟล์ที่เปิดในโหมด 'r +' แต่สะดวกและมีประสิทธิภาพน้อยลงเนื่องจากคุณต้องอ่านและจัดเก็บเนื้อหาของไฟล์ชั่วคราวจากตำแหน่งแทรกไปยัง EOF - ซึ่งอาจ ใหญ่


14

ตามที่กล่าวไว้โดย Adam คุณจะต้องคำนึงถึงข้อ จำกัด ของระบบของคุณก่อนจึงจะสามารถตัดสินใจได้ว่าคุณมีหน่วยความจำเพียงพอที่จะอ่านข้อมูลทั้งหมดลงในหน่วยความจำหรือไม่

หากคุณกำลังเผชิญกับไฟล์ขนาดเล็กหรือไม่มีปัญหาเรื่องความจำนี่อาจช่วยได้:

ตัวเลือก 1) อ่านไฟล์ทั้งหมดลงในหน่วยความจำทำการทดแทน regex ทั้งหมดหรือบางส่วนของบรรทัดแล้วแทนที่ด้วยบรรทัดนั้นบวกกับบรรทัดเพิ่มเติม คุณจะต้องตรวจสอบให้แน่ใจว่า 'เส้นกลาง' ไม่ซ้ำกันในไฟล์หรือถ้าคุณมีการประทับเวลาในแต่ละบรรทัดสิ่งนี้น่าจะน่าเชื่อถือ

# 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()

ตัวเลือก 2) ขบกลางบรรทัดและแทนที่ด้วยบรรทัดนั้นบวกกับบรรทัดพิเศษ

# 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

เขียนชั้นเรียนขนาดเล็กเพื่อทำสิ่งนี้อย่างหมดจด

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()

จากนั้นคุณสามารถใช้วิธีนี้:

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

สิ่งนี้ไม่ได้ผลสำหรับฉันเป็นการส่วนตัวเพิ่มข้อความลงในไฟล์ แต่จะลบทุกอย่างออกก่อน!
Bret Hawker

แน่นอนว่ามันไม่ได้ผลเลย ความอัปยศเพราะดูเหมือนเป็นความคิดที่ดี
Mario Krušelj

0

หากคุณรู้ว่ายูนิกซ์คุณสามารถลองสิ่งต่อไปนี้:

หมายเหตุ: $ หมายถึงพรอมต์คำสั่ง

สมมติว่าคุณมีไฟล์ my_data.txt พร้อมเนื้อหาดังนี้:

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

จากนั้นใช้osโมดูลที่คุณสามารถใช้sedคำสั่งปกติ

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)

หากคุณไม่ทราบว่ามีสติอยู่ลองดูสิมันมีประโยชน์มาก


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