Редактирование Perl на месте производит мусор

У меня возникли проблемы с редактированием файлов на месте, поскольку я безрезультатно просматривал веб-страницы в течение нескольких часов.

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

Если я правильно понимаю, использование $I^ - это просто сокращение для схемы временного файла - или я ошибаюсь?

Режим "+‹" должен открывать файл как для чтения, так и для записи.

Мой тестовый код на данный момент:

#!/usr/bin/perl
use strict;
use warnings;

open(FILE, "+<", "testingfile") or die "$!";

while (<FILE>) {
    print;
    s/world/WORLD/;
    print FILE $_;
    print;
}

«Тестовый файл» состоит из трех строк, и я просто хочу заменить «мир» на «МИР»:

hello
world
foo

Результат

Когда я запускаю сценарий Perl, создается мусор, и терминал остается висящим до тех пор, пока его не прервут (Ctrl+C):

hello
hello
foo
foo
o
o
llo
llo
ÈÈ'>jËNgs}>¾ØKeh%P8*   *        +       +      p+      ÑÑÀ+    +       p+      p+      ¨° #!/u8in/puse ct;
ÈÈ'>jËNgs}>¾ØKeh%P8*   *        +       +      p+      ÑÑÀ+    +       p+      p+      ¨° #!/u8in/puse ct;

"testingfile" теперь содержит:

hello
world
foo
hello
hello
foo

Я запускаю старый Perl в производственной системе SunOS (Solaris):

This is perl, v5.8.4 built for i86pc-solaris-64int

person tachylatus    schedule 23.06.2015    source источник
comment
Какие изменения вы будете вносить в реальность? Изменить world на WORLD просто, потому что две строки имеют одинаковую длину. Если вы хотите добавить данные в конец файла, то это просто, но если вы хотите укоротить или, что еще хуже, удлинить строки перед концом файла, то это становится намного сложнее.   -  person Borodin    schedule 24.06.2015


Ответы (4)


Самый простой способ — использовать Tie::File, который позволяет редактировать текстовый файл, просто изменяя массив. Он имеет репутацию медленного, но вы узнаете, что он слишком медленный, только попробовав его самостоятельно.

Ваш пример кода станет просто таким

#!/usr/bin/perl
use strict;
use warnings;

use Tie::File;

tie my @file, 'Tie::File', 'testingfile' or die $!;

s/world/WORLD/ for @file;

untie @file;
person Borodin    schedule 24.06.2015

Вам нужно узнать о команде seek для перемещения по файлу. Ваш файловый дескриптор FILE имеет один курсор. После того, как вы прочитали из FILE, его курсор указывает на конец данных, которые вы только что прочитали. Затем вы записываете FILE и перезаписываете не данные, которые только что прочитали, а данные, которые только собирались прочитать.

Вот ваш файл. При первом открытии курсор находится в начале файла.

 h e l l o \n w o r l d \n f o o \n EOF
^

Затем вы читаете строку ввода с помощью операции <FILE>. Это загружает текст "hello\n" в переменную $_ и перемещает курсор FILE:

 h e l l o \n w o r l d \n f o o \n EOF
             ^

Затем ваша замена не удалась и не изменила $_, и вы печатаете содержимое $_ в FILE. Запись начинается с курсора, и вы получаете

 h e l l o \n h e l l o \n f o o \n EOF
                          ^

В следующий раз, когда вы будете читать, вы получите foo\n в $_, переместите курсор в конец файла, а затем перепишите $_ в конце файла.

 h e l l o \n h e l l o \n f o o \n f o o \n EOF
                                            ^

Используйте команду seek для перемещения курсора. Может быть, что-то вроде

open(FILE, "+<", "testingfile") or die "$!";

while (<FILE>) {
    print;
    if (s/world/WORLD/) {
        seek FILE, -length($_), 1;   # move back by length of $_
        print FILE $_;
    }
    print;
}

Как указывает @Borodin, это становится намного сложнее, если вы хотите удлинить или укоротить $_ при перемещении по файлу.

person mob    schedule 24.06.2015
comment
Спасибо за подробное объяснение. Я до сих пор удивляюсь, почему у меня вывод мусора и зависание терминала; Я думаю, я, должно быть, вызвал какой-то баг. В итоге я использовал более простой подход — прочитать файл в память, выполнить изменения и снова открыть файл для записи, если изменения были внесены. Похоже, нет простого способа вставить или удалить байты, только перезаписать существующие? - person tachylatus; 26.06.2015

Редактирование на месте не делает того, что вы делаете. Он переименовывает исходный файл, а затем открывает новый файл с исходным именем. Он читает из переименованного файла и записывает в исходное имя файла. См. perlrun для объяснения -I.

person brian d foy    schedule 24.06.2015

person    schedule
comment
На самом деле я сделал вариант этого: Шаг 1: Откройте файл в режиме чтения и сохраните в @lines Шаг 2: Переберите строки, измените содержимое, если это применимо, и запишите любые изменения, увеличив счетчик. Шаг 3: Закройте файл. Шаг 4: Если счетчик $changes не равен нулю, повторно откройте файл в режиме записи и сбросьте содержимое. - person tachylatus; 26.06.2015