流过滤器大量由标准输入的行号指定的行



我有一个巨大的xz压缩文本文件huge.txt.xz有数百万行,太大而无法保持未压缩(60GB)。

我想从那个巨大的文本文件中快速过滤/选择大量行(~1000 行)到文件filtered.txt中。例如,可以选择的行号可以在单独的文本文件中指定,select.txt格式如下:

10
14
...
1499
15858

总的来说,我设想了一个 shell 命令,如下所示,其中"待确定"是我正在寻找的命令:

xz -dcq huge.txt.xz | "TO BE DETERMINED" select.txt >filtered.txt

我设法从一个密切相关的问题中找到了一个几乎可以完成这项工作的awk程序 - 唯一的问题是它需要一个文件名而不是从stdin读取。不幸的是,我并不真正了解awk脚本,并且没有足够的awk来更改它以在这种情况下工作的方式。

这就是现在有效的缺点,即拥有 60GB 文件而不是流式传输:

xz -dcq huge.txt.xz >huge.txt
awk '!firstfile_proceed { nums[$1]; next } 
(FNR in nums)' select.txt firstfile_proceed=1 >filtered.txt

灵感:https://unix.stackexchange.com/questions/612680/remove-lines-with-specific-line-number-specified-in-a-file

与OP当前的想法保持一致:

xz -dcq huge.txt.xz | awk '!firstfile_proceed { nums[$1]; next } (FNR in nums)' select.txt firstfile_proceed=1 -

其中-(在行尾)告诉awk从 stdin 读取(在本例中为通过管道传输到awk调用的xz的输出)。

执行此操作的另一种方法(替换上述所有代码):

awk '
FNR==NR { nums[$1]; next }             # process first file
FNR in nums                            # process subsequent file(s)
' select.txt <(xz -dcq huge.txt.xz)

删除评论并缩减为"单行":

awk 'FNR==NR {nums[$1];next} FNR in nums' select.txt <(xz -dcq huge.txt.xz)

添加一些逻辑来实现 Ed Morton 的评论(一旦 FNR>select.txt中的最大值,就退出处理):

awk '
# process first file
FNR==NR      { nums[$1]
maxFNR= ($1>maxFNR ? $1 : maxFNR)
next
}
# process subsequent file(s):
FNR > maxFNR { exit }
FNR in nums
' select.txt <(xz -dcq huge.txt.xz)

笔记:

  • 请记住,我们正在谈论扫描数百万行输入...
  • FNR > maxFNR显然会给整个操作增加一些CPU/处理时间(尽管时间比FNR in nums少)
  • 如果操作例行需要从文件的最后 25% 中提取行,那么FNR > maxFNR可能提供很少的好处(并且可能会减慢操作速度)
  • 如果操作例行地在文件的前 50% 中找到所有需要的行,那么FNR> maxFNR可能值得花费 CPU/处理时间来防止扫描整个输入流(再说一次,xz操作,在整个文件上,可能是最大的时间消耗)
  • 最终结果:额外的NFR > maxFNR测试可能会加快/减慢整个过程,具体取决于在典型运行中需要处理多少输入流;OP 需要运行一些测试以查看整体运行时是否存在(明显)差异

澄清我之前的评论。我将展示一个简单的可重现示例:

linelist内容:

10
15858
14
1499

为了模拟长输入,我将使用seq -w 100000000

将sed解决方案与我的建议进行比较,我们有:

#!/bin/bash
time (
sed 's/$/p/' linelist > selector
seq -w 100000000 | sed -nf selector
)
time (
sort -n linelist | sed '$!{s/$/p/};$s/$/{p;q}/' > my_selector
seq -w 100000000 | sed -nf my_selector
)

输出:

000000010
000000014
000001499
000015858
real    1m23.375s
user    1m38.004s
sys 0m1.337s
000000010
000000014
000001499
000015858
real    0m0.013s
user    0m0.014s
sys 0m0.002s

将我的解决方案与awk进行比较:

#!/bin/bash
time (
awk '
# process first file
FNR==NR      { nums[$1]
maxFNR= ($1>maxFNR ? $1 : maxFNR)
next
}
# process subsequent file(s):
FNR > maxFNR { exit }
FNR in nums
' linelist <(seq -w 100000000)
)
time (
sort -n linelist | sed '$!{s/$/p/};$s/$/{p;q}/' > my_selector
sed -nf my_selector <(seq -w 100000000)
)

输出:

000000010
000000014
000001499
000015858
real    0m0.023s
user    0m0.020s
sys 0m0.001s
000000010
000000014
000001499
000015858
real    0m0.017s
user    0m0.007s
sys 0m0.001s

在我的结论中,使用qseqawk解决方案相当。对于可读性和可维护性,我更喜欢awk解决方案。

无论如何,这个测试很简单,只对小比较有用。例如,我不知道如果我针对真正的压缩文件(带有大量磁盘 I/O)对此进行测试会是什么结果。

>编辑 by Ed Morton:

任何导致所有输出值都小于一秒的速度测试都是糟糕的测试,因为:

  1. 一般来说,没有人关心X是在0.1秒还是0.2秒内运行,除非在大循环中被调用,否则它们都足够快,并且
  2. 缓存之类的事情会影响结果,并且
  3. 通常,对于执行速度无关紧要的小型输入集运行速度
  4. 更快的脚本对于执行速度确实很重要的大型输入集,运行速度会变慢(例如,如果对于小输入较慢的脚本花费时间设置数据结构,使其在较大的输入中运行得更快)

上面示例的问题在于它只尝试打印 4 行,而不是 OP 说他们必须选择的 1000 行,因此它不会练习 sed 和 awk 解决方案之间的差异,导致 sed 解决方案比 awk 解决方案慢得多,即 sed 解决方案必须测试每行输入的每个目标行号,而 awk解决方案只对当前行执行单个哈希查找。它是输入文件每一行上的 order(N) 与 order(1) 算法。

下面是一个更好的示例,显示从 1000000行文件中打印每 100 行(即将选择 1000 行)而不是从任何大小文件中打印 4 行:

$ cat tst_awk.sh
#!/usr/bin/env bash
n=1000000
m=100
awk -v n="$n" -v m="$m" 'BEGIN{for (i=1; i<=n; i+=m) print i}' > linelist
seq "$n" |
awk '
FNR==NR {
nums[$1]
maxFNR = $1
next
}
FNR in nums {
print
if ( FNR == maxFNR ) {
exit
}
}
' linelist -

$ cat tst_sed.sh
#!/usr/bin/env bash
n=1000000
m=100
awk -v n="$n" -v m="$m" 'BEGIN{for (i=1; i<=n; i+=m) print i}' > linelist
sed '$!{s/$/p/};$s/$/{p;q}/' linelist > my_selector
seq "$n" |
sed -nf my_selector

$ time ./tst_awk.sh > ou.awk
real    0m0.376s
user    0m0.311s
sys     0m0.061s

$ time ./tst_sed.sh > ou.sed
real    0m33.757s
user    0m33.576s
sys     0m0.045s

如您所见,awk 解决方案的运行速度比 sed 解决方案快 2 个数量级,并且它们产生了相同的输出:

$ diff ou.awk ou.sed
$

如果我使输入文件变大并通过设置从中选择 10,000 行:

n=10000000
m=1000

在每个脚本中,对于 OP 的使用来说可能变得更加现实,差异变得非常令人印象深刻:

$ time ./tst_awk.sh > ou.awk
real    0m2.474s
user    0m2.843s
sys     0m0.122s

$ time ./tst_sed.sh > ou.sed
real    5m31.539s
user    5m31.669s
sys     0m0.183s

即 awk 在 2.5 秒内运行,而 sed 需要 5.5 分钟!

如果您有行号文件,请将p添加到每个行号的末尾,并将其作为sed脚本运行。

如果linelist包含

10
14
1499
15858

然后sed 's/$/p/' linelist > selector创建

10p
14p
1499p
15858p

然后

$: for n in {1..1500}; do echo $n; done | sed -nf selector
10
14
1499

我没有发送足够的行来匹配 15858,因此没有打印。

这与从文件解压缩的工作方式相同。

$: tar xOzf x.tgz | sed -nf selector
10
14
1499

相关内容

  • 没有找到相关文章

最新更新