Как я могу удалить последние N строк файла? - PullRequest
3 голосов
/ 06 декабря 2008

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

Ответы [ 11 ]

13 голосов
/ 06 декабря 2008

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

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

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

edit: grepsedawk напоминает нам о опции -n для head, без wc необходимо:

head -n -125000 FILE > NEWFILE
6 голосов
/ 07 декабря 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! $!";
4 голосов
/ 06 декабря 2008

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

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

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

4 голосов
/ 06 декабря 2008

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

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

:1234567,$d

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

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

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

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

3 голосов
/ 06 декабря 2008
  1. перейти в конец файла: fseek
  2. считать столько строк назад
  3. узнать позицию файла: ftell
  4. обрезать файл до этой позиции как длина: ftruncate
0 голосов
/ 20 октября 2009

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

#! /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;

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

0 голосов
/ 19 октября 2009

Мое предложение, используя ed:

printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile
0 голосов
/ 19 октября 2009

попробуйте

:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c)))
0 голосов
/ 20 июня 2009

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

my $ i = 0;
sed -i '\ $ d' имя файла while ($ i ++

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

0 голосов
/ 08 декабря 2008

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

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

...