我需要删除文件中多次出现的所有行。
例子:
Line1
Line2
Line3
Line2
结果:
Line1
Line3
Python、Perl 或 unix-util 无关紧要。谢谢你。
保留顺序,但在内存中保留文件的两个副本:
my @lines;
my %seen;
while (<>) {
push @lines, $_;
++$seen{$_};
}
for (@lines) {
print if $seen{$_} == 1;
}
作为一个单行:
perl -ne'push @l, $_; ++$s{$_}; }{ for (@l) { print if $s{$_} == 1; }'
不保留顺序,但仅在内存中保留文件的一份副本:
my %seen;
++$seen{$_} while <>;
while (my ($k, $v) = each(%seen)) {
print $k if $v == 1;
}
作为一个单行:
perl -ne'++$s{$_}; }{ while (my ($k, $v) = each(%s)) { print $k if $v == 1; }'
这是一个 Python 实现。
如果您需要保留行的初始顺序:
import collections
import fileinput
lines = list(fileinput.input())
counts = collections.Counter(lines)
print(''.join(line for line in lines if counts[line] == 1))
如果没有,它会更简单更快):
import collections
import fileinput
counts = collections.Counter(fileinput.input())
print(''.join(line for line, count in counts.iteritems() if count==1))
对于每一行,您需要查看它是否有任何重复。如果您不想以二次方式执行此操作(执行一次,然后为每行执行第二次),则需要使用中间数据结构,该结构允许您在两次线性遍历中执行此操作。
因此,您通过列表来构建一个哈希表(collections.Counter
这是一个专门dict
的,只是将每个键映射到它出现的次数)。然后,您可以通过列表进行第二次遍历,在哈希表中查找每一个(第一个版本),或者只是迭代哈希表(第二个)。
据我所知,没有办法用命令行工具做同样的事情。您至少必须sort
输入(这是 O(N log N),而不是 O(N)),或者使用隐式执行等效操作的工具。
但对于许多用例来说,这没什么大不了的。对于 1M 行的 80MB 文件,N log N 仅比 N 慢一个数量级,完全可以想象,两个工具之间的常数乘数差异将处于同一数量级。
快速计时测试验证,在 1M 行的规模上,该sort | uniq -u
版本慢了 6 倍多一点,但仍然足够快,您可能不会在意(不到 10 秒,这比复制和粘贴所花费的时间要长) Python 代码,对吗?)除非你必须重复这样做。
通过进一步的测试,在 128K 行时,Python 版本仅快 4 倍;在 64M 行时,速度提高了 28 倍;在 5G 线路上……这两个版本都将系统驱动到交换系统的严重程度,以至于我终止了测试。(Counter
用dbm
键值数据库替换 可以解决这个问题,但对于较小的规模来说成本很高。)
*nix 命令uniq可以做到这一点。
sort file.name | uniq -u
下面是 perl 中的一个示例:
my %line_hash;
open my $fh, "<", "testfile";
while(my $line = <$fh>) {
$line_hash{$line}++;
}
close $fh;
open my $out_fh, ">>", "outfile";
for my $key ( sort keys %line_hash ){
print $out_fh $key if $line_hash{$key} == 1;
}
close $out_fh;
测试文件:
$ cat testfile
Line1
Line2
Line3
Line2
输出文件:
$ cat outfile
Line1
Line3
sort inputfile | uniq -u
(假设 gnu coreutils uniq)
虽然SUSv4说:
-u 禁止写入输入中重复的行。
从评论到某些答案,并非所有 uniq 都以相同的方式解释。
读取每一行,grep 同一文件中的行以查找计数,仅打印计数为 1 的行:
#!/bin/bash
while read line
do
if [ `grep -c ${line} sample.txt` -eq 1 ] ; then echo ${line} ; fi
done < sample.txt