3

有人可以给出一些关于如何从 Perl 中删除文件的最后 n 行的提示吗?我有一个大约 400 MB 的非常大的文件,我想从中删除大约 125,000 行的最后一行。

4

11 回答 11

13

您可以使用Tie::File将文件作为数组处理。

使用领带::文件;
tie (@File, 'Tie::File', $Filename);
拼接 (@File, -125000, 125000);
解开@File;

另一种方法是在 shell中使用head和。wc -l

编辑: grepsedawk 提醒我们-n选项 to head,没有wc必要:

头 -n -125000 文件 > 新文件
于 2008-12-05T23:38:06.927 回答
6

正如人们已经建议的那样,Tie::Array 可以很好地完成工作,如果您想手动完成,我将列出基本算法。有一些草率、缓慢的方法可以很好地处理小文件。这是处理大文件的有效方法。

  1. 查找文件中从末尾数第 N 行之前的位置。
  2. 在那之后截断所有内容(使用truncate())。

1是棘手的部分。我们不知道文件中有多少行或它们在哪里。一种方法是将所有行数起来,然后返回第 N 行。这意味着我们每次都必须扫描整个文件。更有效的是从文件末尾向后读取。您可以这样做,但使用File::ReadBackwardsread()更容易,它可以逐行向后(同时仍然使用有效的缓冲读取)。

这意味着您只阅读了 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! $!";
于 2008-12-07T06:17:38.303 回答
4

你知道有多少行,或者关于这个文件还有其他线索吗?你必须一遍又一遍地这样做,还是只做一次?

如果我必须这样做一次,我会在 vim 中加载文件,查看最后一行号,然后从我想要的最后一行删除直到最后:

:1234567,$d

一般的编程方式是分两遍进行:一是确定行数,二是去掉行数。

简单的方法是将正确数量的行打印到新文件中。它仅在周期和磁盘抖动方面有效,但大多数人都有很多。perlfaq5中的一些东西应该会有所帮助。你完成了工作,然后继续生活。

尽管( )
   {
   打印$输出;
   最后如果 $。> $last_line_I_want;
   }

如果这是您必须做的很多事情或者数据太大而无法重写它,您可以创建行和字节偏移的索引并将文件truncate()到正确的大小。当你保留索引时,你只需要发现新的行尾,因为你已经知道你离开的地方。一些文件处理模块可以为您处理所有这些。

于 2008-12-05T23:47:06.160 回答
4

对于这个问题,我只会使用一个 shell 脚本:

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

(tac 类似于 cat,但以相反的顺序打印行。作者 Jay Lepreau 和 David MacKenzie。GNU coreutils 的一部分。)

于 2008-12-06T01:32:39.863 回答
3
  1. 转到文件末尾:fseek
  2. 倒数那么多行
  3. 找出文件位置: ftell
  4. 将文件截断到该位置作为长度:ftruncate
于 2008-12-06T00:19:39.370 回答
0

Schwern:你脚本中的use Fnctl$rbw->get_handle行是必要的吗?此外,我建议truncate在它不返回 true 的情况下报告错误。

-- Douglas Hunter(如果可以的话,他会评论那篇文章)

于 2008-12-08T01:06:38.480 回答
0

试试这个代码:

我的 $i =0 ;
sed -i '\$d' 文件名 while( $i++ < n ) ;

反引号也将在那里,但我无法打印它们:(

于 2009-06-20T06:46:58.467 回答
0

试试这个

:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c)))
于 2009-10-19T08:50:37.067 回答
0

我的建议,使用ed

printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile
于 2009-10-19T09:01:16.047 回答
0

此示例代码将在扫描文件时保留最后 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;

这有一个额外的好处,它只为最后十个索引和当前行占用了足够的内存。

于 2009-10-20T06:29:51.423 回答
-1

最有效的方法是查找文件末尾,然后增量读取段,同时计算每个段中的换行数,然后使用 truncate(请参阅 perldoc -f truncate)将其修剪掉。CPAN 上还有一两个模块用于向后读取文件。

于 2008-12-06T15:48:14.613 回答