Как удалить последние 5 строк файла

У меня есть несколько команд, печатающих текст в файл с помощью perl. Во время этих print команд у меня есть оператор if, который должен удалить последние 5 строк файла, в который я сейчас пишу, если оператор верен. Количество строк для удаления всегда будет равно 5.

if ($exists == 0) {
  print(OUTPUT ???) # this should remove the last 5 lines
}

person charles hendry    schedule 17.02.2012    source источник
comment
проверьте этот вопрос: stackoverflow.com/questions/345513/   -  person asf107    schedule 17.02.2012
comment
Из Stack Overflow и официального FAQ по Perl: or-append-to-the-beginning">Как изменить, удалить или вставить строку в файл или добавить в начало файла в Perl?   -  person daxim    schedule 17.02.2012


Ответы (6)


Вы можете использовать Tie::File:

use Tie::File;
tie my @array, 'Tie::File', filename or die $!;

if ($exists == 0) {
    $#array -= 5;
}

Вы можете использовать тот же массив при печати, но вместо этого используйте push:

push @array, "line of text";
person TLP    schedule 17.02.2012
comment
Как только я прочитал заголовок, я подумал, что Tie::File FTW! Ты подтолкнул меня на это. +1 - person Joel Berger; 17.02.2012

Я могу придумать только очевидные способы:

  1. Заблокировать файл, сканировать назад, чтобы найти позицию и использовать усекать.
  2. Не печатайте в файл напрямую, пройдите через буфер длиной не менее 5 строк и обрежьте буфер.
  3. Напечатайте маркер, означающий «игнорировать последние пять строк». Обработайте все ваши файлы перед их чтением с помощью буфера, как в № 2.

Все они довольно неудобны, но, боюсь, такова природа плоских файлов.

ХТН

person Richard Huxton    schedule 17.02.2012
comment
Не то чтобы это плохие предложения, но в Tie::File есть встроенная магия. - person Joel Berger; 17.02.2012
comment
@ Джоэл Бергер, не так. Tie::File считывает файл с самого начала, поэтому для больших файлов это определенно будет намного медленнее, чем (1). Не говоря уже о всей памяти, которую он израсходовал бы при создании индекса каждой строки в файле. - person ikegami; 18.02.2012
comment
@ikegami, вы правы в том, что №1 быстрее (для больших файлов). Все, что я на самом деле имел в виду, это то, что Tie::File сделает большую часть работы, а не то, что это будет более эффективно. - person Joel Berger; 18.02.2012

В качестве альтернативы распечатайте весь файл, кроме последних 5 строк:

open($fh, "<", $filename) or die "can't open $filename for reading: $!";
open($fh_new, ">", "$filename.new") or die "can't open $filename.new: $!";
my $index = 0; # So we can loop over the buffer
my @buffer;
my $counter = 0;
while (<$fh>) {
    if ($counter++ >= 5) {
        print $fh_new $buffer[$index];
    }
    $buffer[$index++] = $_;
    $index = 0 if 5 == $index;
}
close $fh;
close $fh_new;
use File::Copy;
move("$filename.new", $filename) or die "Can not copy $filename.new to $filename: $!";
person DVK    schedule 17.02.2012
comment
Это № 2 из ответа Ричарда Хакстона. - person DVK; 17.02.2012
comment
$index = 0 if 5 == $index также можно записать как $index %= 5 с шагом в единицу. - person TLP; 17.02.2012
comment
Это очень странные открытые звонки. open возвращает неопределенное истинное значение в случае успеха и ноль в случае неудачи — ни одно из этих значений не является полезным в качестве дескрипторов файлов. $file - это то место, где должен быть дескриптор файла, но он не объявлен, и вы используете одну и ту же переменную дважды. Также $filename.new требует двойных кавычек вокруг него, иначе он не будет компилироваться. - person Borodin; 17.02.2012
comment
@Borodin - мозговая икота - еще не было кофеина. Ты совершенно прав. - person DVK; 17.02.2012

Файл::ReadBackwards+truncate является самым быстрым для больших файлов и, вероятно, таким же быстрым, как и все остальное для коротких файлов.

use File::ReadBackwards qw( );

my $bfh = File::ReadBackwards->new($qfn)
   or die("Can't read \"$qfn\": $!\n");

$bfh->readline() or last for 1..5;

my $fh = $bfh->get_handle();
truncate($qfn, tell($fh))
   or die $!;

Tie::File является самым медленным и использует большой объем памяти. Избегайте этого решения.

person ikegami    schedule 17.02.2012

вы можете попробовать что-то вроде этого:

open FILE, "<", 'filename';
if ($exists == 0){
 @lines = <FILE>;
 $newLastLine = $#lines - 5;   
 @print = @lines[0 .. $newLastLine];
 print "@print";
}

или даже сокращенно:

open FILE, "<", 'filename';
@lines = <FILE>;
if ($exists == 0){
 print "@lines[0 .. $#lines-5]";
}
person ashraf    schedule 18.02.2012

person    schedule
comment
@TLP: head: unknown option -- - и usage: head [-count | -n count] [file ...]. Это несовместимо с POSIX. - person tchrist; 18.02.2012
comment
@TLP И что? Это не POSIX. Это не стандартно. Это прозаический местный вариант. У меня есть по крайней мере 3 системы, где это с треском проваливается. Вы не должны ожидать, что другие люди будут запускать нестандартное программное обеспечение. Люди, живущие в оранжерее Linux, не знают, что такое реальный мир снаружи. - person tchrist; 18.02.2012
comment
Я бы взял head -n -5 вместо tac в конкурсе портативности в любой день. - person socket puppet; 18.02.2012
comment
@socketpocket Просто псевдоним tac для perl -e 'print reverse <>' — я всегда так делаю. :) Честно говоря, я, вероятно, имел это за десятилетия до того, как tac (1) начал поставляться. Черт возьми, у меня был tac до Perl, черт возьми! - person tchrist; 18.02.2012
comment
@tchrist Я думаю, ты здесь несколько негативен. Дело в том, что лучше посмотреть, можно ли использовать head в вашей системе, чем реверсировать файл, распечатать и снова реверсировать. - person TLP; 18.02.2012
comment
@TLP: если вы собираетесь предложить стандартные инструменты Unix, предоставьте версию, в которой не используются длинные параметры (как это сделал кукольный сокет). Длинные параметры являются расширением GNU, а не переносимыми. - person ninjalj; 19.02.2012