2008-12-05 4 views
3

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

+1

Это должно было быть faq. Подождите, подождите минуту. * type type type совершить *. Теперь это в perlfaq5. :) – 2009-10-20 07:02:40

ответ

13

Вы можете использовать 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
+1

+1 Мне нравится идея оболочки. Это было бы моим первоначальным подходом. Особенно, если это одно. – 2008-12-05 23:44:12

+0

Да, я просто использовал wc и head, и кажется, что это работает .. :) – anand 2008-12-05 23:48:18

+0

На самом деле, я думаю, что в этом случае скрипт perl масштабируется лучше, потому что он не пишет файл заново. – Svante 2008-12-05 23:54:34

4

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

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

:1234567,$d 

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

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

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

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

3
  1. идти до конца файла: FSEEK
  2. счета в обратном направлении, что многие линии
  3. выяснить позицию файла: ftell
  4. усечь файл в таком положении, как длина: ftruncate
4

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

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

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

-1

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

6

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

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

1 является сложной частью. Мы не знаем, сколько строк в файле или где они находятся. Один из способов - подсчитать все линии, а затем вернуться к Nth. Это означает, что мы должны каждый раз сканировать весь файл. Более эффективным было бы чтение назад с конца файла. Вы можете сделать это с помощью 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! $!"; 
0

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

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

0

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

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

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

0

попробовать этот

:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c))) 
0

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

printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile 
0

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

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

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

Смежные вопросы