Шукайте та замінюйте рядок у файлі на Python


292

Я хочу перевести вміст текстового файлу і здійснити пошук і заміну в деяких рядках і записати результат назад у файл. Я міг спочатку завантажити весь файл у пам'ять, а потім записати його назад, але це, мабуть, не найкращий спосіб зробити це.

Який найкращий спосіб зробити це в межах наступного коду?

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

Відповіді:


191

Я думаю, щось подібне повинно це зробити. В основному він записує вміст у новий файл і замінює старий файл новим файлом:

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
Цей код змінює дозволи на вихідний файл. Як я можу зберегти оригінальні дозволи?
нік

1
у чому сенс фх, ви використовуєте його під час закритого дзвінка, але я не бачу сенсу створювати файл лише для його закриття ...
Wicelo

2
@Wicelo Щоб закрити дескриптор файлу, його потрібно закрити. Ось гідне пояснення: logilab.org/17873
Thomas Watnedal,

1
Так, я виявив, що mkstemp()повертається 2-х карт, і (fh, abs_path) = fh, abs_pathя цього не знав, коли задав питання.
Віцело

271

Найкоротшим способом, ймовірно, було б використання модуля 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 у файл. Подивіться на DOCS
Brice

32
Ключовим бітом тут є кома в кінці заяви про друк: вона придушує заяву друку, додаючи ще один новий рядок (оскільки рядок вже має такий). Але це зовсім не очевидно (саме тому Python 3 змінив цей синтаксис, на щастя).
VPeric

4
Зверніть увагу, що це не спрацьовує, коли ви надаєте файл, який відкриває файл, наприклад, коли ви намагаєтесь читати / записувати закодовані файли UTF-16.
bompf

5
Для python3,print(line, end='')
Ч.Ідея

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). Не створюється виняток, рядок просто залишається незмінним.
Девід Уоллес

Працював ідеально і для мене. Я натрапив на ряд інших прикладів, які виглядали дуже схоже на це, але фокусом було використання цього sys.stdout.write(line). Знову дякую!
Мудрець

Якщо я використовую це, мій файл буде порожнім. Будь-яка ідея?
Хав'єр Лопес

Я використовую це
Ракіб Фіга

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
print додає новий рядок, який вже може бути там. щоб уникнути цього, додайте .rstrip () наприкінці своїх замін
Гійом Джендр

Замість використання файлів arg в input (), це може бути fileinput.input (inplace = 1) і викликати скрипт як> python substitu.py myfiles * .txt
chespinoza

24

На основі відповіді Томаса Ватнедала. Однак це не відповідає точно рядковій частині оригінального питання. Функція все ще може замінюватися на основі рядка

Ця реалізація замінює вміст файлу без використання тимчасових файлів, тому що дозволи на збереження файлів залишаються незмінними.

Також re.sub замість заміни дозволяє замінити регулярні вирази замість простої заміни тексту.

Читання файлу як єдиного рядка замість рядка за рядком дозволяє проводити збіг між рядками та заміну.

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

Якщо ви хочете загальну функцію, яка замінює будь-який текст на якийсь інший текст, це, мабуть, найкращий спосіб, особливо якщо ви любитель регексу:

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

Більш пітонічним способом було б використання контекстних менеджерів, як код нижче:

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)

Ви можете знайти повний фрагмент тут .


У Python> = 3.1 ви можете відкрити два менеджери контексту в одному рядку .
florisla

4

Створіть новий файл, скопіюйте рядки зі старого на новий та виконайте заміну перед тим, як записати рядки в новий файл.


4

Розширюючи відповідь @ Kiran, яка, я згоден, є більш лаконічною та пітонічною, це додає кодеків для підтримки читання та написання 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)

Чи збирається зберегти дозвіл старого файлу в новому файлі?
Бідют

2

Використовуючи відповідь hamishmcn в якості шаблону, я зміг шукати рядок у файлі, який відповідає моєму регексу та замінивши його порожнім рядком.

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
Ви повинні скласти регулярний вислів OUTSIDE for для циклу, інакше це втрата продуктивності
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)

Форматування цього коду Python виглядає не зовсім правильно ... (Я намагався виправити, але не був впевнений, що призначено)
Енді Хейден
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.