Удалить конкретную строку из файла 12 ГБ - PullRequest
0 голосов
/ 27 апреля 2018

Я пытаюсь удалить конкретную строку из текстового файла объемом 12 ГБ.

У меня нет опции sed -i, доступной в HP-UX, а другие опции, такие как сохранение во временный файл, не работают, потому что у меня есть только 20 ГБ свободного места с 12 ГБ, уже использованным текстовым файлом.

Учитывая требования к пространству, я пытаюсь сделать это с помощью Perl.

Это решение работает для удаления последних 9 строк из файла размером 12 ГБ.

#!/usr/bin/env perl

use strict;
use warnings;

use Tie::File;

tie my @lines, 'Tie::File', 'test.txt' or die "$!\n";
$#lines -= 9;
untie @lines;

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

1 Ответ

0 голосов
/ 27 апреля 2018

Tie :: File никогда не будет ответом.

  • Это безумно медленно.
  • Он может использовать больше памяти, чем просто выгребать весь файл в память, даже если вы ограничите размер его буфера.

Вы столкнулись с обеими этими проблемами. Вы встречаете каждую строку файла, поэтому Tie :: File прочитает весь файл и сохранит индекс каждой строки в памяти. Это занимает 28 байтов на строку в 64-битной сборке Perl (не считая каких-либо накладных расходов в распределителе памяти).


Чтобы удалить последние 9 строк файла, вы можете использовать следующее:

use File::ReadBackwards qw( );

my $qfn = '...';

my $pos;
{
   my $bw = File::ReadBackwards->new($qfn)
      or die("Can't open \"$qfn\": $!\n");

   for (1..9) {
      defined( my $line = $bw->readline() )
         or last;
   }

   $pos = $bw->tell();
}

# Can't use $bw->get_handle because it's a read-only handle.
truncate($qfn, $pos)
   or die("Can't truncate \"$qfn\": $!\n");

Чтобы удалить произвольную строку, вы можете использовать следующее:

my $qfn = '...';

open(my $fh_src, '<:raw', $qfn)
   or die("Can't open \"$qfn\": $!\n");    
open(my $fh_dst, '+<:raw', $qfn)
   or die("Can't open \"$qfn\": $!\n");

while (<$fh_src>) {
   next if $. == 9;  # Or "if /keyword/", or whatever condition you want.

   print($fh_dst $_)
      or die($!);
}

truncate($fh_dst, tell($fh_dst))
   or die($!);    

Следующая оптимизированная версия предполагает удаление только одной строки (или блока строк):

use Fcntl qw( SEEK_CUR SEEK_SET );

use constant BLOCK_SIZE => 4*1024*1024;

my $qfn = 'file';

open(my $fh_src, '<:raw', $qfn)
   or die("Can't open \"$qfn\": $!\n");
open(my $fh_dst, '+<:raw', $qfn)
   or die("Can't open \"$qfn\": $!\n");

my $dst_pos;
while (1) {
   $dst_pos = tell($fh_src);
   defined( my $line = <$fh_src> )
      or do {
         $dst_pos = undef;
         last;
      };

   last if $. == 9;  # Or "if /keyword/", or whatever condition you want.
}

if (defined($dst_pos)) {
   # We're switching from buffered I/O to unbuffered I/O,
   # so we need to move the system file pointer from where the
   # buffered read left off to where we actually finished reading.
   sysseek($fh_src, tell($fh_src), SEEK_SET)
      or die($!);

   sysseek($fh_dst, $dst_pos, SEEK_SET)
      or die($!);

   while (1) {
      my $rv = sysread($fh_src, my $buf, BLOCK_SIZE);
      die($!) if !defined($rv);
      last if !$rv;

      my $written = 0;
      while ($written < length($buf)) {
         my $rv = syswrite($fh_dst, $buf, length($buf)-$written, $written);
         die($!) if !defined($rv);
         $written += $rv;
      }
   }

   # Must use sysseek instead of tell with sysread/syswrite.    
   truncate($fh_dst, sysseek($fh_dst, 0, SEEK_CUR))
      or die($!);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...