给定一个每行一个非负整数的文件 L 和一个文本文件 F,有什么快速方法可以只保留 F 中那些行号出现在文件 L 中的行?
例子:
$ cat L.txt
1
3
$ cat F.txt
Hello World
Hallo Welt
Hola mundo
$ command-in-question -x L.txt F.txt
Hello World
Hola mundo
我正在寻找一个可以处理具有 5 亿或更多条目的文件 L 的命令;文件 L 按数字排序。
注意:我正在实现 a 的一半command-in-question
,但我只是想知道是否也可以在这里使用一些 Unix 工具。
更新:感谢所有的答案,我今天学到了很多!我想接受多个答案,但这是不可能的。
我从当前答案中采取了最快的解决方案,并将它们放入一个独立的工具中:过滤线。
答案1
grep -n | sort | sed | cut
( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F
那应该很快就能起作用(下面包括一些定时测试)输入任意大小。以下是一些注意事项:
export LC_ALL=C
- 因为以下操作的目的是让整个文件
./F
与其./L
lineno 的文件内联堆叠,所以我们真正需要担心的唯一字符是 ASCII[0-9]
数字和:
冒号。 - 因此,与涉及 UTF-8 的情况相比,在 128 个可能的集合中查找这 11 个字符更容易。
- 因为以下操作的目的是让整个文件
grep -n ''
- 这将插入字符串
LINENO:
进入 stdin - 或 中每一行的头部<./F
。
- 这将插入字符串
sort -t: -nmk1,1 ./L -
sort
根本忽略对其输入文件进行排序,而是(正确)假设它们已预先排序并按排序顺序-m
对它们进行 erge-numerically
,基本上忽略任何可能-k1,1
出现的-t:
冒号字符之外的任何内容。- 虽然这可能需要一些临时空间来完成(取决于某些序列可能发生的距离有多远),与正确的排序相比,它不需要太多,而且会非常快,因为它涉及零回溯。
sort
将输出一个流,其中任何 lineno./L
都将立即位于 中的相应行之前./F
。./L
的线路总是排在第一位,因为它们较短。
sed /:/d\;n
- 如果当前行与冒号匹配,
/:/
则将d
其从输出中删除。否则,自动打印当前行和n
下一行。 - 所以将输出
sed
修剪为sort
仅有的与冒号和下一行不匹配的连续行对 - 或者,仅与./L
下一行匹配。
- 如果当前行与冒号匹配,
cut -sd: -f2-
cut
-s
从输出中抑制那些不包含至少一个其-d:
分隔符字符串的输入行 - 因此./L
的行被完全修剪。- 对于那些这样做的行,它们的第一个
:
冒号分隔的-f
字段消失了- 所有插入的 linenocut
也消失了。grep
小输入测试
seq 5 | sed -ne'2,3!w /tmp/L
s/.*/a-z &\& 0-9/p' >/tmp/F
...生成 5 行示例输入。然后...
( export LC_ALL=C; </tmp/F \
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
)| head - /tmp[FL]
...印刷...
==> standard input <==
a-z 1& 0-9
a-z 4& 0-9
a-z 5& 0-9
==> /tmp/F <==
a-z 1& 0-9
a-z 2& 0-9
a-z 3& 0-9
a-z 4& 0-9
a-z 5& 0-9
==> /tmp/L <==
1
4
5
更大的定时测试
我创建了几个相当大的文件:
seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L
...将 500 万行放入其中/tmp/F
,并将其中随机选择的 150 万行放入/tmp/L
.然后我做了:
time \
( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F |wc - l
它打印:
1500000
grep -n '' \
0.82s user 0.05s system 73% cpu 1.185 total
sort -t: -nmk1,1 /tmp/L - \
0.92s user 0.11s system 86% cpu 1.185 total
sed /:/d\;n \
1.02s user 0.14s system 98% cpu 1.185 total
cut -sd: -f2- \
0.79s user 0.17s system 80% cpu 1.184 total
wc -l \
0.05s user 0.07s system 10% cpu 1.183 total
(我在那里添加了反斜杠)
在当前提供的解决方案中,这是所有解决方案中最快的,但与上面在我的计算机上生成的数据集进行比较时是最快的。在其他人中,只有一位接近争夺第二名,那就是 meuh 的perl
这里。
这绝不是最初提供的解决方案——由于其他人提供的建议/灵感,它减少了三分之一的执行时间。请参阅帖子历史记录以获取较慢的解决方案(但为什么?)。
另外,值得注意的是,如果不是我的系统的多 CPU 架构以及该管道中每个进程的并发执行,其他一些答案可能会更好。它们都同时工作——每个都在自己的处理器核心上——传递数据并完成整体的一小部分。它太酷了。
但最快的解决方案是...
但这不是最快的解决方案。毫无疑问,这里提供的最快的解决方案是C程序。我叫它cselect
。将其复制到我的 X 剪贴板后,我将其编译为:
xsel -bo | cc -xc - -o cselect
然后我做了:
time \
./cselect /tmp/L /tmp/F |
wc -l
...结果是...
1500000
./cselect /tmp/L /tmp/F \
0.50s user 0.05s system 99% cpu 0.551 total
wc -l \
0.05s user 0.05s system 19% cpu 0.551 total
答案2
我会使用awk
,但不会将 的全部内容存储L.txt
在内存中并进行不必要的哈希查找;-)。
list=L.txt file=F.txt
LIST="$list" awk '
function nextline() {
if ((getline n < list) <=0) exit
}
BEGIN{
list = ENVIRON["LIST"]
nextline()
}
NR == n {
print
nextline()
}' < "$file"
答案3
我会用awk
:
awk 'NR==FNR {a[$1]; next}; FNR in a' L.txt F.txt
更新:我已经完成了绩效衡量;似乎这个版本对于非常大的数据集(如规定的要求的情况)可以更好地扩展,因为比较非常快并且过度补偿了构建哈希表所需的工作。
答案4
只是为了完整性:我们可以合并 Stéphane Chazelas 答案中优秀的 awk 脚本和 kos 答案中的 perl 脚本,但不将整个列表保留在内存中,希望 perl 可能比 awk 更快。 (我更改了参数的顺序以匹配原始问题)。
#!/usr/bin/env perl
use strict;
die "Usage: $0 l f\n" if $#ARGV+1 != 2;
open(L,$ARGV[0]) or die "$ARGV[0]: $!";
open(F,$ARGV[1]) or die "$ARGV[1]: $!";
while(my $number = <L>){
#chop $number;
while (<F>) {
if($. == $number){
print;
last;
}
}
}