流过滤大量由标准输入中的行号指定的行
Stream filter large number of lines that are specified by line number from stdin
我有一个巨大的 xz
压缩文本文件 huge.txt.xz
,其中包含数百万行,由于太大而无法在未压缩的情况下保存 (60GB)。
我想快速 filter/select 大量行(~1000s)从那个巨大的文本文件到一个文件filtered.txt
。 select 的行号可以在单独的文本文件 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[]; next }
(FNR in nums)' select.txt firstfile_proceed=1 >filtered.txt
与OP当前的想法保持一致:
xz -dcq huge.txt.xz | awk '!firstfile_proceed { nums[]; next } (FNR in nums)' select.txt firstfile_proceed=1 -
-
(在行尾)告诉 awk
从 stdin 读取(在这种情况下,来自 xz
的输出被管道传输到 awk
打电话)。
另一种方法(替换所有上述代码):
awk '
FNR==NR { nums[]; next } # process first file
FNR in nums # process subsequent file(s)
' select.txt <(xz -dcq huge.txt.xz)
删除评论并缩减为 'one-liner':
awk 'FNR==NR {nums[];next} FNR in nums' select.txt <(xz -dcq huge.txt.xz)
添加一些逻辑来实现 Ed Morton 的评论(一旦 FNR > 来自 select.txt
的最大值就退出处理):
awk '
# process first file
FNR==NR { nums[]
maxFNR= (>maxFNR ? : maxFNR)
next
}
# process subsequent file(s):
FNR > maxFNR { exit }
FNR in nums
' select.txt <(xz -dcq huge.txt.xz)
备注:
- 请记住,我们正在谈论扫描数百万行输入...
FNR > maxFNR
显然会为整体操作增加一些 cpu/processing 时间(虽然比 FNR in nums
少)
- 如果操作通常需要从文件的最后 25% 中提取行,那么
FNR > maxFNR
可能没有什么好处(并且可能减慢操作速度)
- 如果操作通常在文件的前 50% 中找到所有需要的行,那么
FNR> maxFNR
可能值得 cpu/processing 时间来避免扫描整个输入流(然后同样,对整个文件的 xz
操作可能是最大的时间消耗者)
- 最终结果:额外的
NFR > maxFNR
测试可能 speed-up/slow-down 整个过程取决于在典型的 运行 中需要处理多少输入流; OP 需要 运行 一些测试来查看整体 运行time 是否存在(明显的)差异
如果您有行号文件,请将 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
澄清我之前的评论。我将展示一个简单的可重现示例:
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[]
maxFNR= (>maxFNR ? : 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
在我的结论中,seq
使用 q
与 awk
解决方案相当。为了可读性和可维护性,我更喜欢 awk
解决方案。
无论如何,这个测试很简单,只对小的比较有用。我不知道,例如,如果我用重光盘 I/O.
对真实的压缩文件进行测试,结果会是什么
Ed Morton 编辑:
任何导致所有输出值小于一秒的速度测试都是错误的测试,因为:
- 一般来说,没有人关心 X 运行 是在 0.1 秒还是 0.2 秒内,它们都足够快,除非在大循环中被调用,并且
- 缓存之类的事情会影响结果,
- 通常,对于执行速度无关紧要的小输入集 运行 速度较快的脚本对于执行速度很重要的大输入集来说 运行 较慢(例如,如果脚本这对于小输入来说更慢花时间设置数据结构,这将允许它 运行 对于更大的输入更快)
上面例子的问题是它只是试图打印 4 行而不是 OP 所说的他们必须 select 的 1000 行,所以它没有行使 sed 之间的区别导致 sed 解决方案比 awk 解决方案慢得多的 awk 解决方案是 sed 解决方案必须测试每一行输入的每个目标行号,而 awk 解决方案只对当前行进行一次散列查找。这是输入文件每一行的 order(N) 与 order(1) 算法。
这里有一个更好的例子,显示了从 1000000 行文件中每第 100 行打印一次(即 select 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[]
maxFNR =
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
$
如果我通过设置使输入文件变大 select 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 运行s 在 2.5 秒内,而 sed 需要 5.5 分钟!
我有一个巨大的 xz
压缩文本文件 huge.txt.xz
,其中包含数百万行,由于太大而无法在未压缩的情况下保存 (60GB)。
我想快速 filter/select 大量行(~1000s)从那个巨大的文本文件到一个文件filtered.txt
。 select 的行号可以在单独的文本文件 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[]; next }
(FNR in nums)' select.txt firstfile_proceed=1 >filtered.txt
与OP当前的想法保持一致:
xz -dcq huge.txt.xz | awk '!firstfile_proceed { nums[]; next } (FNR in nums)' select.txt firstfile_proceed=1 -
-
(在行尾)告诉 awk
从 stdin 读取(在这种情况下,来自 xz
的输出被管道传输到 awk
打电话)。
另一种方法(替换所有上述代码):
awk '
FNR==NR { nums[]; next } # process first file
FNR in nums # process subsequent file(s)
' select.txt <(xz -dcq huge.txt.xz)
删除评论并缩减为 'one-liner':
awk 'FNR==NR {nums[];next} FNR in nums' select.txt <(xz -dcq huge.txt.xz)
添加一些逻辑来实现 Ed Morton 的评论(一旦 FNR > 来自 select.txt
的最大值就退出处理):
awk '
# process first file
FNR==NR { nums[]
maxFNR= (>maxFNR ? : maxFNR)
next
}
# process subsequent file(s):
FNR > maxFNR { exit }
FNR in nums
' select.txt <(xz -dcq huge.txt.xz)
备注:
- 请记住,我们正在谈论扫描数百万行输入...
FNR > maxFNR
显然会为整体操作增加一些 cpu/processing 时间(虽然比FNR in nums
少)- 如果操作通常需要从文件的最后 25% 中提取行,那么
FNR > maxFNR
可能没有什么好处(并且可能减慢操作速度) - 如果操作通常在文件的前 50% 中找到所有需要的行,那么
FNR> maxFNR
可能值得 cpu/processing 时间来避免扫描整个输入流(然后同样,对整个文件的xz
操作可能是最大的时间消耗者) - 最终结果:额外的
NFR > maxFNR
测试可能 speed-up/slow-down 整个过程取决于在典型的 运行 中需要处理多少输入流; OP 需要 运行 一些测试来查看整体 运行time 是否存在(明显的)差异
如果您有行号文件,请将 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
澄清我之前的评论。我将展示一个简单的可重现示例:
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[]
maxFNR= (>maxFNR ? : 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
在我的结论中,seq
使用 q
与 awk
解决方案相当。为了可读性和可维护性,我更喜欢 awk
解决方案。
无论如何,这个测试很简单,只对小的比较有用。我不知道,例如,如果我用重光盘 I/O.
对真实的压缩文件进行测试,结果会是什么Ed Morton 编辑:
任何导致所有输出值小于一秒的速度测试都是错误的测试,因为:
- 一般来说,没有人关心 X 运行 是在 0.1 秒还是 0.2 秒内,它们都足够快,除非在大循环中被调用,并且
- 缓存之类的事情会影响结果,
- 通常,对于执行速度无关紧要的小输入集 运行 速度较快的脚本对于执行速度很重要的大输入集来说 运行 较慢(例如,如果脚本这对于小输入来说更慢花时间设置数据结构,这将允许它 运行 对于更大的输入更快)
上面例子的问题是它只是试图打印 4 行而不是 OP 所说的他们必须 select 的 1000 行,所以它没有行使 sed 之间的区别导致 sed 解决方案比 awk 解决方案慢得多的 awk 解决方案是 sed 解决方案必须测试每一行输入的每个目标行号,而 awk 解决方案只对当前行进行一次散列查找。这是输入文件每一行的 order(N) 与 order(1) 算法。
这里有一个更好的例子,显示了从 1000000 行文件中每第 100 行打印一次(即 select 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[]
maxFNR =
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
$
如果我通过设置使输入文件变大 select 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 运行s 在 2.5 秒内,而 sed 需要 5.5 分钟!