假设样本文件a.txt内容如下:

hello world!
hello world!
I love regex.
hello world!
I love regex.
hello world!

简单观察可知,hello world!共重复4行;I love regex.重复2行。如何使用正则表达式来写一个程序,统计这些数据呢?因为现实中需要统计的文件,绝非是只凭肉眼就能观察出来。我想到了两种方法,第一种方法,是依赖于正则表达式(否则这篇文章也不会贴在这里);第二种,hash表做主角,正则表达式作绿叶。

正则表达式的解法

思路是:对于任何一行文本,如果后面若干行[0~EOF)之后,如果存在相同的文本行,则记下该行内容,统计出现次数;然后删除这样的文本行,再进行下一行的统计。输出统计结果。

下面是相应的perl程序,附注释。

#!/usr/bin/perl 
#usage:  ./dup_re.pl <a.txt
 
undef $/;           # enable "slurp" mode
my $file = <STDIN>;          # whole file now here
 
while($file =~ m
    /                   #for each line;
        ^\s*            #ignore the whitespaces at both ends; 
        (\S.*?)         #get the line content, save to $1;
        \s*$            #ignore empty lines by using \S
        .*?             #check if there is the same pattern of $1
        ^\s*\1\s*$              #after 0 or more lines;
    /smx) 
{
    my $line=$1;                        
    my $count= $file =~ s/$line//g;     #delete the duplicated lines
                       #save the number to $count;
                                       #ignore empty lines
        print $count,"times:\t",$line,"\n";
}

Hash表解法

这种方法,受益于perl语言本身的强大的hash表功能。思路如下:

  • 建立空的hash表;
  • 逐行读取文件;
  • 以文本内容为key,插入到表中来。如果是首次出现,value为0,否则value++。
  • 输出hash表中value>=2的记录。

Perl程序:

#!/usr/bin/perl
#usage:  ./dup_hash.pl a.txt
 
my %hash=();
while(<>)
{
   if (/^\s*(\S.*?)\s*$/)           #ignore whitespaces at both ends; 
   {                                #ignore empty lines by using \S
        $hash{$1}++;                #save the line to $1, and count the time it appears
   }
}
 
#sort the hash by values; 
foreach $key (sort { $hash{$b} <=> $hash{$a} } keys %hash) { 
    if ($hash{$key}>=2)             #only print the lines that duplicates;
    {                               #for all results, just remove the 'if' line
       printf "%d times:\t%s\n", $hash{$key}, $key;
    }
}

结果

上面的程序分别保存为dup_re.pl,dup_hash.pl。由于程序对于外部文件的读取的方法不同,运行方式也有差别,详见下图:
我爱正则表达式|统计重复文本行的两种方法

Update

忽然想到,如果要让这脚本更有效,可以指定忽略大小写,忽略单词间多个空格的情况,使得Hello world!   hello   WORLd! 被视为重复行。测试了一下,正则式没让我失望。