如何从 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 > 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(<>)
循环中读取大文件,这样您就不会完全浪费内存。在每一行上,检查密钥 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 个文件之一中删除公共行?的主要内容,如果未能解决你的问题,请参考以下文章