Удаление строки из огромного файла в Perl
У меня есть огромный текстовый файл, и первые пять строк этого читаются как ниже:
This is fist line
This is second line
This is third line
This is fourth line
This is fifth line
Теперь я хочу написать что-то в произвольной позиции третьей строки этого файла, которая заменит символы в этой строке новой строкой, которую я пишу. Я могу добиться этого с помощью приведенного ниже кода:
use strict;
use warnings;
my @pos = (0);
open my $fh, "+<", "text.txt";
while(<$fh) {
push @pos, tell($fh);
}
seek $fh , $pos[2]+1, 0;
print $fh "HELLO";
close($fh);
Тем не менее, я не могу понять с тем же подходом, как я могу удалить всю третью строку из этого файла, чтобы текст читал ниже:
This is fist line
This is second line
This is fourth line
This is fifth line
Я не хочу читать весь файл в массив, и при этом я не хочу использовать Tie::File. Можно ли выполнить мое требование с помощью поиска и скажите? Решение будет очень полезно.
3 ответа
Файл представляет собой последовательность байтов. Мы можем заменить (перезаписать) некоторые из них, но как бы мы их удалили? Как только файл записан, его байты не могут быть "извлечены" из последовательности или "очищены" каким-либо образом. (Те в конце файла могут быть отклонены, обрезая файл по мере необходимости.)
Остальная часть содержимого должна двигаться вверх, так что то, что следует за удаляемым текстом, перезаписывает его. Мы должны переписать остальную часть файла. На практике часто гораздо проще переписать весь файл.
Как очень простой пример
use warnings 'all';
use strict;
use File::Copy qw(move);
my $file_in = '...';
my $file_out = '...'; # best use `File::Temp`
open my $fh_in, '<', $file_in or die "Can't open $file_in: $!";
open my $fh_out, '>', $file_out or die "Can't open $file_out: $!";
# Remove a line with $pattern
my $pattern = qr/this line goes/;
while (<$fh_in>)
{
print $fh_out $_ unless /$pattern/;
}
close $fh_in;
close $fh_out;
# Rename the new fie into the original one, thus replacing it
move ($file_out, $file_in) or die "Can't move $file_out to $file_in: $!";
Это записывает каждую строку входного файла в выходной файл, если строка не соответствует заданному шаблону. Затем этот файл переименовывается, заменяя оригинальный (что не требует копирования данных). Смотрите эту тему в perlfaq5.
Поскольку мы действительно используем временный файл, я бы порекомендовал для этого основной модуль File::Temp.
Это можно сделать более эффективным, но гораздо более сложным, открыв в обновлении '+<'
режим, чтобы перезаписать только часть файла. Итерируешь до строки с шаблоном, записываешь (tell
) его положение и длину строки, затем скопируйте все оставшиеся строки в памяти. затем seek
вернуться к позиции минус длина этой строки, и сбросить скопированный остаток файла, перезаписав строку и все, что следует за ней.
Обратите внимание, что теперь данные для остальной части файла копируются дважды, хотя одна копия находится в памяти. Переход к этой проблеме может иметь смысл, если удаляемая строка находится в очень большом файле. Если есть больше строк, чтобы удалить это становится грязнее.
Запись нового файла и копирование его поверх оригинала изменяет номер инода файла. Это может быть проблемой для некоторых инструментов или процедур, и если это так, вы можете вместо этого обновить оригинал либо
Как только новый файл будет записан, откройте его для чтения и откройте оригинал для записи. Это забивает оригинальный файл. Затем прочитайте из нового файла и запишите в исходный, таким образом скопировав содержимое обратно в тот же индекс. Удалите новый файл, когда закончите.
Откройте исходный файл в режиме чтения-записи (
'+<'
) начать с. Как только новый файл написан,seek
в начало оригинала (или в место, откуда нужно перезаписать) и запишите в него содержимое нового файла. Не забудьте также установить конец файла, если новый файл короче,truncate $fh, tell($fh);
после копирования сделано. Это требует некоторой осторожности, и первый способ, как правило, безопаснее.
Если файл не был огромным, новый "файл" может быть "записан" в память в виде массива или строки.
Использование sed
команда из командной строки Linux в Perl:
my $return = `sed -i '3d' text.txt`;
Где "3d" означает удалить 3-й ряд.
Полезно посмотреть на perlrun
и посмотрите, как perl сам изменяет файл "на месте".
Дано:
$ cat text.txt
This is fist line
This is second line
This is third line
This is fourth line
This is fifth line
Вы, очевидно, можете "модифицировать на месте", например, используя -i
а также -p
переключиться, чтобы вызвать Perl:
$ perl -i -pe 's/This is third line\s*//' text.txt
$ cat text.txt
This is fist line
This is second line
This is fourth line
This is fifth line
Но если вы обратитесь к рецепту Perl Cookbook 7.9 (или посмотрите на perlrun), вы увидите, что это:
$ perl -i -pe 's/This is third line\s*//' text.txt
эквивалентно:
while (<>) {
if ($ARGV ne $oldargv) { # are we at the next file?
rename($ARGV, $ARGV . '.bak');
open(ARGVOUT, ">$ARGV"); # plus error check
select(ARGVOUT);
$oldargv = $ARGV;
}
s/This is third line\s*//;
}
continue{
print;
}
select (STDOUT); # restore default output