LINUX.ORG.RU

Чтение и замена определенной строки в Python

 ,


0

1

Приветствую. Требуется найти определенную строку, изменить её и записать на место старой в файле. В принципе эта задача решена, однако при записи в файл, вместо записи только новой строки, записывается полное содержимое этого файла с измененной строкой (дописывается в конец). Перерыл документацию, кучу примеров, но так и не понял как действовать в данном случае:

    def applyScheme(self):
        scheme_path = self.schemePath.text()

        with open("/home/sunderland93/config", "r") as f:
            lines = f.readlines()
            for i in range(len(lines)):
                if "set $theme" in lines[i]:
                    line = lines[i].strip()

        new_scheme = line.replace(str(line), 'set $theme ' + str(scheme_path))
        
        with open("/home/sunderland93/config", "r+") as w:
            lines2 = w.read()
            scheme = lines2.replace(str(line), str(new_scheme))
            w.write(scheme)
Как правильно это реализовать?

Чтобы перемещаться по открытому файлу, есть системный вызов lseek (2). В Python доступ к нему осуществляется через метод объекта seek.

Не понятно, зачем вы открываете файл два раза, зачем вызов lines2 = w.read()

Как я понимаю, достаточно один раз прочитать файл, найти и заменить нужную строку, вызвать f.seek(), чтобы переместиться в начало файла. Перезаписать файл новыми данными.

anarquista ★★★★★
()
Ответ на: комментарий от vvn_black

Мне нужно просто заменить одну строку в существующем конфиге. На ту что у меня в переменной «new_scheme»

Sunderland93 ★★★★★
() автор топика
Последнее исправление: Sunderland93 (всего исправлений: 1)

Код, мягко говоря, очень плохой. Вопрос: зачем эти постоянные приведения к строке: str(line), str(new_scheme), даже там, где предельно очевидно, что объект и так является строкой?..

new_scheme = line.replace(str(line), 'set $theme ' + str(scheme_path))

эквивалентно (т.к. line.replace(line) заменяет всю строку целиком по определению)

new_scheme = f'set $theme {scheme_path}'

только мой вариант в разы понятнее и быстрее.

Понимаю, что хочется х*к-х*к и накалякать скрпит, но всё же… Ведь кровь из глаз у читателя!

emorozov
()
Последнее исправление: emorozov (всего исправлений: 1)
Ответ на: комментарий от emorozov

Вот так?

    def applyScheme(self):
        scheme_directory = self.schemePath.text()

        with open(sway_config, "r") as f:
            lines = f.readlines()
            for i in range(len(lines)):
                 if "set $theme" in lines[i]:
                     line = lines[i].strip()

        scheme_path = f'set $theme {scheme_directory}'

        with open(sway_config, "r+") as w:
            conf = w.read()
            new_scheme = conf.replace(line, scheme_path)
            w.seek(0)
            w.truncate()
            w.write(new_scheme)

Sunderland93 ★★★★★
() автор топика
Ответ на: комментарий от Sunderland93

Учитывая, что это конфиг, то есть размер в любом случае не более десятка-другого килобайт, гораздо проще:

  1. прочитать целиком
  2. сделать замену в памяти
  3. записать

Как-то так:

    def applyScheme(self):
        scheme_directory = self.schemePath.text()

        with open(sway_config, "r") as f:
            config = f.read()

        if "set $theme" in config:
            config = re.sub(r'set \$theme.+$', f'set $theme {scheme_directory}')
            with open(sway_config, "w") as f:
                f.write(config)

Это не совсем эквивалентный код, но, скорее всего, на практике разницы не будет.

Если pathlib использовать, то можно ещё на пару строк короче и понятнее сделать.

Но и так получилось намного короче и понятнее, и нет ненужного двойного чтения файла.

Edit: перечитал и немного исправил попытавшись лучше понять изначальный замысел автора.

emorozov
()
Последнее исправление: emorozov (всего исправлений: 1)
Ответ на: комментарий от Sunderland93

Чем же )

from pathlib import Path

filename = 'config'
filename_bak = Path(filename).rename(Path(filename).with_suffix('.bak'))

with open(filename_bak, 'r') as fr, open(filename, 'w') as fw:
    for line in fr:
        if '*' in line:
            fw.write(line.replace('*', '#'))
        else:
            fw.write(line)

Upd. Или даже так, если вынести логику замены отдельно и это небольшой текстовик:

def change_line(line):
    if '*' in line:
        return line.replace('*', '#')

    return line

with open(filename_bak, 'r') as fr, open(filename, 'w') as fw:
    fw.write(''.join(map(change_line, fr)))

vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 2)
Ответ на: комментарий от vvn_black

Ну вот кто ж так делает? Сначала создают dot-new, и только после того как он удачно записался rotate’ают plain -> dot-old, dot-new -> plain (при желании делается так что plain существует в каждый момент времени).

bugfixer ★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)