Jak zmodyfikować plik tekstowy?


175

Używam Pythona i chciałbym wstawić ciąg do pliku tekstowego bez usuwania lub kopiowania pliku. Jak mogę to zrobić?


1
Możesz odnieść się do tej odpowiedzi Alexa Martelli.
Alok



@Ani drugi post jest i tak duplikatem Wstawiania linii w określonym miejscu w pliku tekstowym i na pewno są tutaj jasne, skomponowane odpowiedzi. Dlaczego nie dodać odpowiedzi tutaj zamiast w inny sposób? Przyjęta odpowiedź nie jest warunkiem dobrego pytania.
Bhargav Rao

@BhargavRao Głosowanie wycofane. Powinienem był znaleźć ten duplikat!
Ani Menon

Odpowiedzi:


134

Niestety nie ma możliwości wstawienia do środka pliku bez ponownego zapisania. Jak wskazywały poprzednie plakaty, możesz dołączyć do pliku lub nadpisać jego część za pomocą wyszukiwania, ale jeśli chcesz dodać rzeczy na początku lub w środku, musisz go przepisać.

To kwestia systemu operacyjnego, a nie Pythona. Tak samo jest we wszystkich językach.

Zwykle czytam z pliku, wprowadzam modyfikacje i zapisuję je do nowego pliku o nazwie myfile.txt.tmp lub czegoś podobnego. Jest to lepsze niż wczytywanie całego pliku do pamięci, ponieważ plik może być na to za duży. Po utworzeniu pliku tymczasowego zmieniam jego nazwę na taką samą, jak oryginalnego pliku.

Jest to dobry, bezpieczny sposób na zrobienie tego, ponieważ jeśli zapis do pliku ulegnie awarii lub zostanie przerwany z jakiegokolwiek powodu, nadal masz nienaruszony oryginalny plik.


3
Czy narzędzia unixowe, takie jak awk / sed, robią coś podobnego w swoim kodzie?
Manish Gill

Nie jest prawdą, że jest to takie samo we wszystkich językach. W języku ActionScript: fileStream.openAsync (nazwa pliku, FileMode.UPDATE); Następnie mogę przejść do dowolnego miejsca w pliku i zmienić wszystko.
AndrewBenjamin,

2
@AndrewBenjamin Czy wiesz, jakie wywołania systemowe wykonuje ActionScript? Czy istnieje możliwość, że openAsync czyta plik i zapisuje nowy po wywołaniu?
AlexLordThorsen

@Rawrgulmuffins Nie mam. Wiem jednak, że nie wczytuje on całego pliku do pamięci, ponieważ używałem go do obsługi plików o rozmiarze kilku GB. Podejrzewam, że to to samo, co pisanie za pomocą streamwritera C #. Postrzegam Pythona jako narzędzie do szybkiego robienia małych rzeczy, a nie jako programowanie na dużą skalę i manipulowanie plikami.
AndrewBenjamin

4
@AndrewBenjamin, użytkownik nie pyta o przeszukiwanie pliku i zmienianie go (każdy język, który znam, może to zrobić); pyta o wstawienie tekstu, co różni się od zwykłej zmiany / nadpisania tego, co już jest w pliku. Może w praktycznym zastosowaniu jest inaczej, ale nic, co mogę znaleźć w API ActionScript, nie wskazuje, że zachowuje się on inaczej niż jakikolwiek inny język w tym zakresie.
eestrada

104

Zależy od tego, co chcesz zrobić. Aby dołączyć, możesz otworzyć go za pomocą „a”:

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

Jeśli chcesz coś wstępnie przygotować, musisz najpierw przeczytać z pliku:

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
Tylko mały dodatek, aby użyć withinstrukcji w Pythonie 2.5, musisz dodać "z przyszłego importu z_statement". Poza tym otwieranie plików z withinstrukcją jest zdecydowanie bardziej czytelne i mniej podatne na błędy niż zamykanie ręczne.
Alexander Kojevnikov,

2
Możesz rozważyć fileinputbibliotekę pomocniczą, która ładnie obsługuje brudną procedurę otwierania / odczytu / modyfikacji / zapisu / zamiany podczas używania inline=Truearg. Przykład tutaj: stackoverflow.com/a/2363893/47390
mikegreenberg

3
Po prostu nie zapomnij zamknąć pliku. f.Close()
D.Rosado

5
To nie jest styl, którego używam, D.Rosado, ale kiedy używam stylu ze stylem, nie sądzę, abyś musiał ręcznie zamykać. Z śledzi zasoby, które tworzy.
Chris

4
Państwo nie trzeba ręcznie zamknąć pliku. O to właśnie chodzi w używaniu „z” tutaj. (Cóż, w rzeczywistości Python robi to, gdy tylko obiekt pliku jest usuwany z pamięci, co w CPythonie ma miejsce, gdy nazwa związana z nim wychodzi poza zakres ... ale inne implementacje tego nie robią, a CPython może przestać to robić pewnego dnia , więc „z” jest zalecane)
Jürgen A. Erhard

71

fileinputModuł biblioteki standardowej Pythona będzie przepisać inplace pliku, jeśli używasz inplace = 1 parametr:

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
Jak to ma działać w python3? Właśnie przeportowałem aplikację, która miała taki kod z pythona na python3 i po prostu nie mogłem go w ogóle poprawnie uruchomić. Zmienna „line” jest typem bajtów, próbowałem zdekodować ją do Unicode, a następnie zmodyfikować, a następnie zakodować z powrotem do bajtów, ale po prostu nie działałaby dobrze. To wywołało wyjątek, którego nie pamiętam. Czy ludzie używają fileinput inplace = 1 w python3 z jakimś sukcesem?
robru

1
@Robru: oto kod Pythona 3
jfs

13
Ale to żaden problem, ponieważ najpierw przetestowałeś go na nieistotnym pliku, prawda?
Paula Livingstone

33

Przepisanie pliku w miejscu często polega na zapisaniu starej kopii pod zmienioną nazwą. Użytkownicy Uniksa dodają a, ~aby zaznaczyć stary. Ludzie z Windows robią różne rzeczy - dodają .bak lub .old - lub całkowicie zmieniają nazwę pliku lub umieszczają ~ na początku nazwy.

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

Zamiast tego shutilmożesz użyć następujących.

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

1
Wygląda dobrze. Zastanawiasz się, czy .readlines () jest lepsze niż iterowanie źródła?
bozdoz,

2
@bozdoz: iteracja jest lepsza, ponieważ readlines czyta cały plik. Nie nadaje się do dużych plików. Oczywiście zakłada to, że możesz dokonywać modyfikacji w tak zlokalizowany sposób. Czasami nie możesz lub Twój kod staje się znacznie bardziej skomplikowany.
Jürgen A. Erhard

@ S.Lott: os.rename(aFile, aFile + "~")zmieni nazwę pliku źródłowego, nie tworząc kopii.
Patapoom

14

Moduł mmap w Pythonie pozwoli ci wstawić do pliku. Poniższy przykład pokazuje, jak można to zrobić w systemie Unix (mmap systemu Windows może być inny). Należy pamiętać, że nie obsługuje to wszystkich błędów i może spowodować uszkodzenie lub utratę oryginalnego pliku. Ponadto nie obsługuje to ciągów znaków 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()

Można to również zrobić bez mmap z plikami otwartymi w trybie 'r +', ale jest to mniej wygodne i mniej wydajne, ponieważ musiałbyś czytać i tymczasowo przechowywać zawartość pliku od pozycji wstawienia do EOF - co może być ogromny.


14

Jak wspomniał Adam, musisz wziąć pod uwagę ograniczenia swojego systemu, zanim będziesz mógł zdecydować, czy masz wystarczająco dużo pamięci, aby wczytać wszystko do pamięci, wymień jej części i ponownie zapisz.

Jeśli masz do czynienia z małym plikiem lub nie masz problemów z pamięcią, może to pomóc:

Opcja 1) Wczytaj cały plik do pamięci, wykonaj podstawienie wyrażenia regularnego na całej lub części linii i zastąp ją tą linią plus dodatkową linią. Musisz upewnić się, że „środkowa linia” jest unikalna w pliku lub jeśli w każdej linii znajdują się znaczniki czasu, powinno to być całkiem niezawodny.

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

Opcja 2) Znajdź środkową linię i zastąp ją tą linią plus dodatkową linią.

# 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

Napisałem małą klasę, aby zrobić to porządnie.

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

Następnie możesz go użyć w ten sposób:

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

To nie działa dla mnie osobiście, dodaje tekst do pliku, ale najpierw usuwa wszystko!
Bret Hawker

Rzeczywiście, to w ogóle nie działa. Szkoda, bo to był dobry pomysł.
Mario Krušelj

0

Jeśli znasz jakiegoś Uniksa, możesz spróbować następujących rzeczy:

Uwagi: $ oznacza wiersz polecenia

Załóżmy, że masz plik my_data.txt z taką zawartością:

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

Następnie za pomocą osmodułu możesz użyć zwykłych sedpoleceń

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)

Jeśli nie jesteś świadomy seda, sprawdź to, jest to niezwykle przydatne.


3
To wcale nie jest Pythonic
DarkSuniuM
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.