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

Может кто-нибудь подсказать, как удалить последние n строк из файла в Perl? У меня очень большой файл размером около 400 МБ, и я хочу удалить из него около 125 000 последних строк.


person anand    schedule 05.12.2008    source источник
comment
Это должен был быть вопрос. Подожди, подожди минутку. тип тип тип фиксация. Теперь он в perlfaq5. :)   -  person brian d foy    schedule 20.10.2009


Ответы (11)


Вы можете использовать Tie :: File для обработки файла как массива.

use Tie::File;
tie (@File, 'Tie::File', $Filename);
splice (@File, -125000, 125000);
untie @File;

Альтернативой является использование head и wc -l в оболочке.

изменить: grepsedawk напоминает нам о параметре -n для head, wc не требуется:

head -n -125000 FILE > NEWFILE
person Svante    schedule 05.12.2008
comment
+1 Мне нравится идея оболочки. Это был бы мой первоначальный подход. Особенно, если это разовая вещь. - person Chris Kloberdanz; 06.12.2008
comment
На самом деле, я думаю, что в этом случае сценарий perl лучше масштабируется, потому что он не записывает файл заново. - person Svante; 06.12.2008
comment
Нет необходимости использовать wc: head -n -5 FILE ›NEWFILE ... даст вам ФАЙЛ в NEWFILE за вычетом последних 5 строк - person grepsedawk; 06.12.2008

Поскольку люди уже предложили Tie :: Array, который хорошо выполняет свою работу, я изложу базовый алгоритм, если вы захотите сделать это вручную. Есть небрежные и медленные способы сделать это, хорошо работающие с небольшими файлами. Вот эффективный способ сделать это для больших файлов.

  1. Найдите позицию в файле непосредственно перед N-й строкой с конца.
  2. Обрежьте все, что находится после этой точки (используя truncate()).

1 - сложная часть. Мы не знаем, сколько строк в файле и где они находятся. Один из способов - пересчитать все строки и вернуться к N-му. Это означает, что мы должны каждый раз сканировать весь файл. Более эффективным было бы чтение в обратном направлении с конца файла. Вы можете сделать это с помощью read(), но проще использовать File :: ReadBackwards, который можно в обратном направлении построчно (при этом все еще используются эффективные буферизованные чтения).

Это означает, что вы читаете только 125 000 строк, а не весь файл. truncate() должен быть O (1) и атомарным и почти ничего не стоить, независимо от размера файла. Он просто сбрасывает размер файла.

#!/usr/bin/perl

use strict;
use warnings;

use File::ReadBackwards;

my $LINES = 10;     # Change to 125_000 or whatever
my $File = shift;   # file passed in as argument

my $rbw = File::ReadBackwards->new($File) or die $!;

# Count backwards $LINES or the beginning of the file is hit
my $line_count = 0;
until( $rbw->eof || $line_count == $LINES ) {
    $rbw->readline;
    $line_count++;
}

# Chop off everything from that point on.
truncate($File, $rbw->tell) or die "Could not truncate! $!";
person Community    schedule 07.12.2008
comment
Я бы побеспокоился о масштабируемости с привязкой. Вы не можете обрабатывать файл, размер которого превышает доступную виртуальную память, к тому же вам нужно прочитать все, что может занять много времени в большом файле. Решение fseek одновременно быстрое и масштабируемое. - person James Anderson; 20.10.2009
comment
Я думаю, вы путаете галстук с чем-то еще. File :: ReadBackwards использует привязку, чтобы предоставить вам интерфейс для работы с файлами, но не считывает весь файл в память. Он читает с конца файла по мере необходимости (с помощью поиска и т. Д.). - person brian d foy; 20.10.2009

Вы знаете, сколько в нем строк, или есть еще какие-нибудь подсказки об этом файле? Вам нужно делать это снова и снова, или это только один раз?

Если бы мне пришлось сделать это один раз, я бы загрузил файл в vim, посмотрел на последний номер строки, а затем удалил бы из последней строки, которую хочу, до конца:

:1234567,$d

Общий способ программирования состоит в том, чтобы сделать это в два прохода: один для определения количества строк, а второй - для удаления строк.

Самый простой способ - вывести нужное количество строк в новый файл. Это эффективно только с точки зрения циклов и, возможно, небольшой перегрузки диска, но у большинства людей их много. Некоторые вещи из perlfaq5 должны помочь. Вы выполняете работу и продолжаете жить.

while(  )
   {
   print $out;
   last if $. > $last_line_I_want;
   }

Если это то, что вам нужно сделать много или размер данных слишком велик для их перезаписи, вы можете создать индекс строк и байтовых смещений и truncate () файл до нужного размера. Пока вы сохраняете индекс, вам нужно только обнаруживать новые окончания строк, потому что вы уже знаете, где остановились. Некоторые модули обработки файлов могут справиться со всем этим за вас.

person brian d foy    schedule 05.12.2008

Я бы просто использовал сценарий оболочки для этой проблемы:

tac file | sed '1,125000d' | tac

(tac похож на cat, но печатает строки в обратном порядке. Авторы Джея Лепро и Дэвида Маккензи. Часть GNU coreutils.)

person Norman Ramsey    schedule 06.12.2008
comment
Вы должны передать это в файл в конце. Кроме того, вам не нужно использовать этот хак tac, head делает то, что вы хотите (также из coreutils). - person Svante; 06.12.2008
comment
Конечно, или вы могли бы использовать сценарий перезаписи из книги Керниган и Пайк. Раскрась меня глупо, но как голова может это сделать? (tac - это не хакер; он часто бывает полезным. Моя частная версия [называемая revlines] у меня уже много лет. Я рад видеть ее в coreutils.) - person Norman Ramsey; 06.12.2008
comment
@Norman - уловка заключается в том, чтобы указать отрицательный аргумент опции -n. Из заголовка (1): с ведущим знаком «-» вывести все, кроме последних N строк каждого файла. - person converter42; 06.12.2008
comment
О сладкий! голова, должно быть, приобрела -n, когда я не смотрел. Поскольку я такой динозавр, я до сих пор пишу такие вещи, как «голова -3 * / README». Я вижу, что это даже не упоминается на странице руководства. Спасибо, что научили меня чему-то новому. - person Norman Ramsey; 07.12.2008

  1. перейти в конец файла: fseek
  2. посчитать в обратном порядке на столько строк
  3. узнать позицию файла: ftell
  4. обрезать файл до этой позиции как длина: ftruncate
person yogman    schedule 06.12.2008

Шверн: Нужны ли строки use Fnctl и $rbw->get_handle в вашем скрипте? Кроме того, я бы рекомендовал сообщать truncate об ошибках, если он не возвращает true.

- Дуглас Хантер (который бы прокомментировал этот пост, если бы мог)

person douglashunter    schedule 08.12.2008
comment
В этих строках не было необходимости. Я подозреваю, что Шверн сначала попытался усечь дескриптор файла непосредственно перед тем, как переключиться на усечение по имени файла. - person brian d foy; 20.10.2009

Попробуйте этот код:

my $ i = 0;
sed -i '\ $ d' filename while ($ i ++ ‹n);

обратные кавычки тоже будут там, но я не могу их распечатать :(

person sud03r    schedule 20.06.2009

Мое предложение с использованием ed:

printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile
person mouviciel    schedule 19.10.2009

попробуй это

:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c)))
person nighteblis    schedule 19.10.2009

Этот пример кода сохранит индекс последних 10 строк при сканировании файла. Затем он использует самый ранний индекс в буфере, чтобы обрезать файл. Это, конечно, будет работать только в том случае, если усечение работает в вашей системе.

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

open my $file, '+<', 'test.in'; # rw
my @list;
while(<$file>){
  if( @list <= 10 ){
    push @list, tell $file;
  }else{
    (undef,@list) = (@list,tell $file);
  }
}

seek $file, 0, 0;
truncate $file, $list[0] if @list;
close $file;

Это имеет дополнительное преимущество, заключающееся в том, что он использует достаточно памяти только для последних десяти индексов и текущей строки.

person Brad Gilbert    schedule 20.10.2009

Самый эффективный способ - перейти к концу файла, затем постепенно прочитать сегменты, подсчитывая количество новых строк в каждом, а затем использовать усечение (см. Perldoc -f truncate), чтобы обрезать его. На CPAN также есть модуль или два для чтения файла в обратном направлении.

person Shlomi Fish    schedule 06.12.2008