Pergunta

Alguém pode dar algumas dicas de como eliminar os últimos n linhas de um arquivo em Perl? Eu tenho um arquivo muito grande de cerca de 400 MB, e eu quero excluir alguns 125.000 últimas linhas da mesma.

Foi útil?

Solução

Você pode usar Tie :: File para manipular o arquivo como uma matriz.

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

Uma alternativa é usar head e wc -l no shell.

edit: grepsedawk nos lembra da opção -n para head, não wc necessário:

head -n -125000 FILE > NEWFILE

Outras dicas

Como as pessoas têm sugerido Tie :: já Array, que faz o trabalho bem, eu vou colocar para fora o algoritmo básico se você quiser fazê-lo com a mão. Há, formas lentas desleixado de fazê-lo que funcionam bem para arquivos pequenos. Aqui é a maneira eficiente de fazê-lo para arquivos grandes.

  1. Encontre a posição no arquivo pouco antes da linha de Nth a partir do final.
  2. truncar tudo depois desse ponto (usando truncate()).

1 é a parte complicada. Não sabemos quantas linhas existem no arquivo ou onde eles estão. Uma maneira é contar todas as linhas para cima e depois voltar para o Nth. Isso significa que temos de digitalizar todo o arquivo de cada vez. Mais eficiente seria ler para trás a partir do final do arquivo. Você pode fazer isso com read() mas é mais fácil de usar File :: ReadBackwards que pode ir para trás linha por linha (enquanto ainda usando tamponada eficiente lê).

Isto significa que você ler apenas 125.000 linhas em vez de todo o arquivo. truncate() deve ser O (1) e atômica e custo quase nada, não importa quão grande é o arquivo. Ele simplesmente redefine o tamanho do arquivo.

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

Você sabe quantas linhas existem, ou há qualquer outro indício sobre este arquivo? Você tem de fazer isso sobre-e-mais uma vez, ou é apenas uma vez?

Se eu tivesse que fazer isso uma vez, eu carregar o arquivo no vim, olhar para o último número da linha, em seguida, excluir a partir da última linha que eu quero até o fim:

:1234567,$d

A maneira programação geral é fazê-lo em duas passagens:. Um para determinar o número de linhas, e, em seguida, um para se livrar das linhas

A maneira mais simples é imprimir o número certo de linhas para um novo arquivo. É apenas eficiente em termos de ciclos e talvez um pouco de goleada disco, mas a maioria das pessoas tem a abundância desses. Algumas das coisas em perlfaq5 deve ajudar. Você começar o trabalho feito e você seguir com a vida.

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

Se isto é algo que você tem que fazer um monte ou o tamanho dos dados é muito grande para reescrevê-lo, você pode criar um índice de linhas e offsets de bytes e truncate () o arquivo para o tamanho certo. Como você manter o índice, você só tem que descobrir as novas terminações de linha porque você já sabe onde você parou. Alguns módulos de manipulação de arquivo pode lidar com tudo isso para você.

Gostaria apenas de usar um script shell para este problema:

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

(tac é como linhas de gato, mas imprime na ordem inversa. Por Jay Lepreau e David MacKenzie. Parte da coreutils GNU.)

  1. ir para o final do arquivo: fseek
  2. contar para trás que muitas linhas
  3. descobrir a posição do arquivo: ftell
  4. arquivo truncar a essa posição como o comprimento: ftruncate

Schwern: As linhas use Fnctl e $rbw->get_handle em seu script necessário? Além disso, eu recomendo relatar erros truncate no caso ele não retorna verdadeiro.

- Douglas Hunter (que têm comentado sobre esse post se ele poderia ter)

Tente este código:

my $ i = 0;
sed -i '\ $ d' arquivo while ($ i ++

crase também estará lá, mas eu sou incapaz de obtê-los impressos: (

A minha sugestão, usando ed:

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

Tente este

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

Este código de exemplo irá manter o índice dos últimos 10 linhas, como ele verifica o arquivo. Em seguida, ele usa o mais antigo índice no buffer, para truncar o arquivo. Isto, obviamente, só funcionará se obras TRUNCATE em seu sistema.

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

Isto tem a vantagem adicional de que ele só usa-se memória suficiente para os últimos índices dez, e a linha atual.

A maneira mais eficiente seria buscar ao final do arquivo, segmentos, em seguida, de forma incremental ler, enquanto contando o número de novas linhas em cada um, e, em seguida, usar truncado (ver perldoc -f truncada) para reduzi-la. Há também um módulo ou dois no CPAN para ler um para trás de arquivo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top