如何从 Perl 中的 2 个文件之一中删除公共行?

Posted

技术标签:

【中文标题】如何从 Perl 中的 2 个文件之一中删除公共行?【英文标题】:How to delete common lines from one of 2 files in Perl? 【发布时间】:2012-06-04 12:05:34 【问题描述】:

我有两个文件,一个小一个,一个大。小文件是大文件的子集。

例如:

小文件:

solar:1000
alexey:2000

大文件:

andrey:1001
solar:1000
alexander:1003
alexey:2000

我想从 Big.txt 中删除所有在 Small.txt 中也存在的行。也就是说,我想删除大文件中小文件共有的行。

所以,我写了一个 Perl Script,如下所示:

#! /usr/bin/perl

use strict;
use warnings;

my ($small, $big, $output) = @ARGV;

open(BIG, "<$big") || die("Couldn't read from the file: $big\n");
my @contents = <BIG>;
close (BIG);

open(SMALL, "<$small") || die ("Couldn't read from the file: $small\n");

while(<SMALL>)

    chomp $_;
    @contents = grep !/^\Q$_/, @contents;


close(SMALL);

open(OUTPUT, ">>$output") || die ("Couldn't open the file: $output\n");

print OUTPUT @contents;
close(OUTPUT);

但是,此 Perl 脚本不会删除 Big.txt 中 Small.txt 共有的行

在这个脚本中,我首先打开大文件流并将整个内容复制到数组@contents 中。然后,我遍历小文件中的每个条目并检查它是否存在于大文件中。我从 Big File 中过滤该行并将其保存回数组中。

我不知道为什么这个脚本不起作用?谢谢

【问题讨论】:

如果您想要非 perl 解决方案:comm -1 -3 file1 file2 &gt; file2 @TLP - 仅限于 GNU grep 吗?而且,它不应该也包括-v吗? @DVK 你得问问别人。是的,-v 反转,可能-x 匹配整行,正如 ysth 所说。 grep 可以做到这一点,但需要更多开关:grep -F -v -x -f smallfile bigfile 【参考方案1】:

您的脚本不起作用,因为 grep 使用 $_ 并从循环中接管(在 grep 期间)您的 $_ 的旧值(例如,您在正则表达式不是用于在 while 块中存储循环值的变量 - 它们的名称相同,但范围不同。

改用命名变量(通常,对于任何超过 1 行的代码,永远不要使用 $_,正是为了避免这种类型的错误):

while (my $line=<SMALL>) 
    chomp $line;
    @contents = grep !/^\Q$line/, @contents;

然而,正如 Oleg 所指出的,更有效的解决方案是将小文件的行读入哈希,然后处理大文件一次,检查哈希内容(我还稍微改进了样式 - 请随意学习和使用未来,使用词法文件句柄变量,3-arg 形式的 open 和通过$! 打印 IO 错误):

#! /usr/bin/perl

use strict;
use warnings;

my ($small, $big, $output) = @ARGV;

use File::Slurp;
my @small = read_file($small);
my %small = map  ($_ => 1)  @small;

open(my $big, "<", $big) or die "Can not read $big: Error: $!\n";
open(my $output, ">", $output) or die "Can not write to $output: Error: $!\n";

while(my $line=<$big>) 
    chomp $line;
    next if $small$line; # Skip common
    print $output "$line\n";


close($big);
close($output);

【讨论】:

grep 解决方案还需要一个结束锚点,或者可能会过滤掉比预期更多的锚点:/^\Q$line\E\n/ @ysth - 虽然不是脚本的主要问题,但恕我直言,您的评论值得单独回答 ..NEVER use $_ for any code longer than 1 line -- 听起来很夸张。我会说作为一般规则,不要使用$_ 的行数超过您(老板?)的熟练程度和记忆力允许的行数。不同的$_ 没有被覆盖,只是被覆盖了,类似于for local $_ (@foo) .. @TLP - 你是对的,用词不当。它“超出了范围”:) 就个人而言,当我在凌晨 2 点在生产紧急情况下阅读代码时,我不想依赖熟练度或记忆力。我希望它尽可能清晰明了。意义,最大的自我记录标识符。 @DVK 可读性很好,我只是认为one line 作为经验法则有点极端。但你是对的,对于最极端的情况,极端措施是谨慎的。【参考方案2】:

它不起作用有几个原因。首先,@content 中的行仍然有它们的换行符。其次,当您将grep 中的$_ 设置为不是小文件的最后一行时,@contents 数组的每个元素,有效地做到这一点:对于列表中的每个元素,返回除此元素之外的所有内容,最后留下空列表。

这并不是真正的好方法 - 您正在读取大文件,然后尝试多次重新处理它。首先,读取一个小文件并将每一行放入哈希中。然后在while(&lt;&gt;) 循环中读取大文件,这样您就不会完全浪费内存。在每一行上,检查密钥 exists 是否在先前填充的哈希中,如果是 - 转到 next 迭代,否则打印该行。

【讨论】:

这是完美的。但他的问题是:为什么他的脚本不起作用?尽管你的方式明显好很多,但他的方式也应该有效,不是吗? 感谢您的帮助。我也会尝试按照您建议的方式使用哈希来实现它。我知道有更好的实施方式,我正在尝试快速完成,但我也会学习您建议的方式。【参考方案3】:

这里有一个小而有效的解决方案:

#!/usr/bin/perl

use strict;
use warnings;

my ($small, $big, $output) = @ARGV;

my %diffx;

open my $bfh, "<", $big or die "Couldn't read from the file $big: $!\n";
# load big file's contents
my @big = <$bfh>;
chomp @big;
# build a lookup table, a structured table for big file
@diffx@big = ();
close $bfh or die "$!\n";

open my $sfh, "<", $small or die "Couldn't read from the file $small: $!\n";
my @small = <$sfh>;
chomp @small;
# delete the elements that exist in small file from the lookup table
delete @diffx@small;
close $sfh;

# print join "\n", keys %diffx;

open my $ofh, ">", $output or die "Couldn't open the file $output for writing: $!\n";
# what is left is unique lines from big file
print $ofh join "\n", keys %diffx;  
close $ofh;

__END__

附:我从Perl Cookbook, 2nd Edition 学到了这个技巧和许多其他技巧。谢谢

【讨论】:

以上是关于如何从 Perl 中的 2 个文件之一中删除公共行?的主要内容,如果未能解决你的问题,请参考以下文章

Shell如何实现从文件中的第n行开始读取记录?

从文件中删除重复行-perl

如何使用 Perl 删除 Excel 工作表中的整列并在新的 Excel 文件中写入更新的数据?

如何在perl中删除哈希值中的重复值?

如何使用 Perl 从文件中删除多行 C 注释?

如何在 Pandas 中删除两个数据框中的公共行?