如何使用Perl从大文件中删除非唯一行?

时间:2009-09-24 11:09:15

标签: perl batch-file dos

使用Perl通过Windows中的批处理文件调用重复数据删除 Windows中的DOS窗口通过批处理文件调用。 批处理文件调用执行操作的Perl脚本。我有批处理文件。 只要数据文件不是太大,我工作的代码脚本就会删除重复数据。 需要解决的问题是数据文件较大(2 GB或更多),在此文件大小时,尝试将完整文件加载到阵列中以便删除重复数据时会发生内存错误。 内存错误发生在以下子程序中: -

@contents_of_the_file = <INFILE>;

(一种完全不同的方法是可以接受的,只要它解决了这个问题,请建议)。 子程序是: -

sub remove_duplicate_data_and_file
{
 open(INFILE,"<" . $output_working_directory . $output_working_filename) or dienice ("Can't open $output_working_filename : INFILE :$!");
  if ($test ne "YES")
   {
    flock(INFILE,1);
   }
  @contents_of_the_file = <INFILE>;
  if ($test ne "YES")
   {
    flock(INFILE,8);
   }
 close (INFILE);
### TEST print "$#contents_of_the_file\n\n";
 @unique_contents_of_the_file= grep(!$unique_contents_of_the_file{$_}++, @contents_of_the_file);

 open(OUTFILE,">" . $output_restore_split_filename) or dienice ("Can't open $output_restore_split_filename : OUTFILE :$!");
 if ($test ne "YES")
  {
   flock(OUTFILE,1);
  }
for($element_number=0;$element_number<=$#unique_contents_of_the_file;$element_number++)
  {
   print OUTFILE "$unique_contents_of_the_file[$element_number]\n";
  }
 if ($test ne "YES")
  {
   flock(OUTFILE,8);
  }
}

6 个答案:

答案 0 :(得分:6)

您无需在@contents_of_the_file中存储原始文件的完整副本,并且 - 如果重复数量相对于文件大小较低 - %unique_contents_of_the_file中的近两个完整副本和{ {1}}。如@unique_contents_of_the_file所述,您可以通过对数据进行两次传递来减少存储要求:(1)分析文件,存储有关非重复行的行号的信息; (2)再次处理文件以将非重复写入输出文件。

这是一个例子。我不知道我是否选择了散列函数的最佳模块(Digest::MD5);也许其他人会对此发表评论。另请注意ire_and_curses的3参数形式,您应该使用它。

open()

答案 1 :(得分:4)

您应该能够使用散列有效地执行此操作。您不需要存储来自行的数据,只需确定哪些是相同的。所以......

  • 不要啜饮 - 一次读一行。
  • 哈希线。
  • 将散列线表示存储为列表的Perl哈希中的键。将行号存储为列表的第一个值。
  • 如果密钥已存在,请将重复的行号附加到与该值对应的列表中。

在此过程结束时,您将拥有一个标识所有重复行的数据结构。然后,您可以再次通过该文件以删除这些重复项。

答案 2 :(得分:2)

Perl使用大文件做英雄事,但2GB可能是DOS / Windows的限制。

你有多少内存?

如果您的操作系统没有抱怨,最好一次读取一行文件,然后立即写入输出。

我正在考虑使用钻石操作符&lt;&gt;但是我不愿意提出任何代码,因为在我发布代码的情况下,我冒犯了一个Perl大师。

我宁愿不冒险。我希望Perl骑兵很快到来。

与此同时,here's一个链接。

答案 3 :(得分:1)

无论文件有多大,这都是有效的解决方案。但它并不专门使用RAM,因此它比基于RAM的解决方案慢。您还可以指定希望此内容使用的RAM量。

该解决方案使用程序将SQLite视为数据库的临时文件。

#!/usr/bin/perl

use DBI;
use Digest::SHA 'sha1_base64';
use Modern::Perl;

my $input= shift;
my $temp= 'unique.tmp';
my $cache_size_in_mb= 100;
unlink $temp if -f $temp;
my $cx= DBI->connect("dbi:SQLite:dbname=$temp");
$cx->do("PRAGMA cache_size = " . $cache_size_in_mb * 1000);
$cx->do("create table x (id varchar(86) primary key, line int unique)");
my $find= $cx->prepare("select line from x where id = ?");
my $list= $cx->prepare("select line from x order by line");
my $insert= $cx->prepare("insert into x (id, line) values(?, ?)");
open(FILE, $input) or die $!;
my ($line_number, $next_line_number, $line, $sha)= 1;
while($line= <FILE>) {
  $line=~ s/\s+$//s;
  $sha= sha1_base64($line);
  unless($cx->selectrow_array($find, undef, $sha)) {
    $insert->execute($sha, $line_number)}
  $line_number++;
}
seek FILE, 0, 0;
$list->execute;
$line_number= 1;
$next_line_number= $list->fetchrow_array;
while($line= <FILE>) {
  $line=~ s/\s+$//s;
  if($next_line_number == $line_number) {
    say $line;
    $next_line_number= $list->fetchrow_array;
    last unless $next_line_number;
  }
  $line_number++;
}
close FILE;

答案 4 :(得分:0)

在“完全不同的方法”类别中,如果你有Unix命令(例如Cygwin):

cat infile | sort | uniq > outfile

这应该有效 - 根本不需要Perl - 这可能会,也可能不会解决你的记忆问题。但是,您将丢失infile的顺序(因为outfile现在将被排序)。

编辑:可以使用以下算法更好地处理大型文件的替代解决方案:

  1. 逐行阅读INFILE
  2. 将每一行哈希到一个小哈希(例如哈希#mod 10)
  3. 将每一行附加到哈希编号唯一的文件(例如tmp-1到tmp-10)
  4. 关闭INFILE
  5. 打开每个tmp-#并将其排序到新文件sortedtmp - #
  6. Mergesort sortedtmp- [1-10](即打开所有10个文件并同时读取),跳过重复项并将每次迭代写入结束输出文件
  7. 对于非常大的文件,这比啜食更安全。

    第2部分&amp; 3可以更改为随机#而不是散列数mod 10.

    这是一个可能有帮助的脚本BigSort(虽然我没有测试过):

    # BigSort
    #
    # sort big file
    #
    # $1 input file
    # $2 output file
    #
    # equ   sort -t";" -k 1,1 $1 > $2
    
    BigSort()
    {
    if [ -s $1 ]; then
      rm $1.split.* > /dev/null 2>&1
      split -l 2500 -a 5 $1 $1.split.
      rm $1.sort > /dev/null 2>&1
      touch $1.sort1
      for FILE in `ls $1.split.*`
      do
        echo "sort $FILE"
        sort -t";" -k 1,1 $FILE > $FILE.sort
        sort -m -t";" -k 1,1 $1.sort1 $FILE.sort > $1.sort2
        mv $1.sort2 $1.sort1
      done
      mv $1.sort1 $2
      rm $1.split.* > /dev/null 2>&1
    else
      # work for empty file !
      cp $1 $2
    fi
    } 
    

答案 5 :(得分:0)

你可以使用命令行perl的内联替换模式。

perl -i~ -ne 'print unless $seen{$_}++' uberbigfilename