仅计算前 X 行输出的最快方法
Fastest way to count just the first X lines of output
我有一个来自 tshark
过滤器的大终端输出,我想检查行数(本例中的包数)是否达到阈值 X。
该操作是在许多大文件的循环中完成的,所以我想在这里将性能提升到最大。
我想知道的是 wc -l
是计算终端命令输出的最快方法。
我的行看起来像这样:(所以 tshark 命令在这里无关紧要,所以为了便于阅读我替换了它)
THRESHOLD=100
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | wc -l) -gt $THRESHOLD ]] || echo "not enough"
虽然这几乎可以正常工作,但我想知道是否有办法在达到阈值后停止。只要达到(或未达到)阈值,确切的数字并不重要。
猜测是:
HEAD=$((THRESHOLD+1))
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | head -n $HEAD | wc -l) -gt $THRESHOLD ]] || echo "not enough"
但是通过管道连接到附加服务并增加阈值可能会更慢,不是吗?
编辑: 将示例代码更改为有效的 tshark 片段
使用包装器启动 tshark
(或 tail -f -n +1 file
),该包装器检查输出行数并在达到阈值后退出。这是 awk 中的一个示例,使用 seq
来模仿 tshark
:
$ awk '
BEGIN {
cmd="seq 1 100" # command to execute, outputs 100 lines
while((cmd|getline res)>0 && ++c<50); # count to 50 lines and exit
print res # test to show last line of input
exit
}'
输出:
50
seq
在 50 之后保持 运行 一段时间,但最终退出。更改 cmd="seq 1 10000000 | tee foo"
和 tail foo
我得到:
...
11407
11408
11
基准
只有一种方法可以找出答案:自己进行基准测试。
下面是我想到的一些实现。
gen() { seq "$max"; }
# functions returning 0 (success) iff `gen` prints less than `$thold` lines
a() { [ "$(gen | head -n"$thold" | wc -l)" != "$thold" ]; }
b() { [ -z "$(gen | tail -n+"$thold" | head -c1)" ]; }
c() { [ "$(gen | grep -cm"$thold" ^)" != "$thold" ]; }
d() { [ "$(gen | grep -Fcm"$thold" '')" != "$thold" ]; }
e() { gen | awk "NR >= $thold{exit 1}"; }
f() { gen | awk -F^ "NR >= $thold{exit 1}"; }
g() { gen | sed -n "$thold"q1; }
h() { mapfile -n1 -s"$thold" < <(gen); [ -z "$MAPFILE" ]; }
max=1''000''000''000
for fn in {a..h}; do
printf '%s: ' "$fn"
for ((thold=1''000''000; thold<=max; thold*=10)); do
printf '%.0e=%2.1fs, ' "$thold" "$({ time -p "$fn"; } 2>&1 | grep -Eom1 '[0-9.]+')"
done
echo
done
在上面的脚本中 gen
是您实际命令的占位符 tsharks output lines
。函数 a
到 g
测试 tsharks
' 的输出是否至少有 $thold
行。你可以像
一样使用它们
a && echo "tsharks printed less than $thold lines"
结果
这些是我系统上的结果:
a: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.8s, 1e+09=8.9s,
b: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.9s, 1e+09=8.4s,
c: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=16.1s,
d: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=15.7s,
e: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=83.2s,
f: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=84.6s,
g: 1e+06=0.0s, 1e+07=0.3s, 1e+08=3.0s, 1e+09=31.6s,
h: 1e+06=7.7s, 1e+07=90.0s, ... (manually aborted)
b: ... 1e+08=0.9s ...
表示方法 b
花了 0.9 秒才发现 seq 1000000000
的输出至少有 1e+08
(= 100'000'000) 行.
结论
从这个答案中提出的方法来看,b
显然是最快的。但是,实际结果可能因系统而异(head
、grep
、...有不同的实现和版本)以及您的实际用例。我建议使用您的实际数据进行基准测试(即,将 gen()
中的 seq
替换为您的 tsharks output lines
并将 thold
设置为任何实际使用的值)。
如果您需要更快的方法,您可以尝试使用 stdbuf
和 LC_ALL=C
。
我有一个来自 tshark
过滤器的大终端输出,我想检查行数(本例中的包数)是否达到阈值 X。
该操作是在许多大文件的循环中完成的,所以我想在这里将性能提升到最大。
我想知道的是 wc -l
是计算终端命令输出的最快方法。
我的行看起来像这样:(所以 tshark 命令在这里无关紧要,所以为了便于阅读我替换了它)
THRESHOLD=100
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | wc -l) -gt $THRESHOLD ]] || echo "not enough"
虽然这几乎可以正常工作,但我想知道是否有办法在达到阈值后停止。只要达到(或未达到)阈值,确切的数字并不重要。
猜测是:
HEAD=$((THRESHOLD+1))
[[ $(tshark -r $file -Y "tcp.stream==${streamID}" | head -n $HEAD | wc -l) -gt $THRESHOLD ]] || echo "not enough"
但是通过管道连接到附加服务并增加阈值可能会更慢,不是吗?
编辑: 将示例代码更改为有效的 tshark 片段
使用包装器启动 tshark
(或 tail -f -n +1 file
),该包装器检查输出行数并在达到阈值后退出。这是 awk 中的一个示例,使用 seq
来模仿 tshark
:
$ awk '
BEGIN {
cmd="seq 1 100" # command to execute, outputs 100 lines
while((cmd|getline res)>0 && ++c<50); # count to 50 lines and exit
print res # test to show last line of input
exit
}'
输出:
50
seq
在 50 之后保持 运行 一段时间,但最终退出。更改 cmd="seq 1 10000000 | tee foo"
和 tail foo
我得到:
...
11407
11408
11
基准
只有一种方法可以找出答案:自己进行基准测试。 下面是我想到的一些实现。
gen() { seq "$max"; }
# functions returning 0 (success) iff `gen` prints less than `$thold` lines
a() { [ "$(gen | head -n"$thold" | wc -l)" != "$thold" ]; }
b() { [ -z "$(gen | tail -n+"$thold" | head -c1)" ]; }
c() { [ "$(gen | grep -cm"$thold" ^)" != "$thold" ]; }
d() { [ "$(gen | grep -Fcm"$thold" '')" != "$thold" ]; }
e() { gen | awk "NR >= $thold{exit 1}"; }
f() { gen | awk -F^ "NR >= $thold{exit 1}"; }
g() { gen | sed -n "$thold"q1; }
h() { mapfile -n1 -s"$thold" < <(gen); [ -z "$MAPFILE" ]; }
max=1''000''000''000
for fn in {a..h}; do
printf '%s: ' "$fn"
for ((thold=1''000''000; thold<=max; thold*=10)); do
printf '%.0e=%2.1fs, ' "$thold" "$({ time -p "$fn"; } 2>&1 | grep -Eom1 '[0-9.]+')"
done
echo
done
在上面的脚本中 gen
是您实际命令的占位符 tsharks output lines
。函数 a
到 g
测试 tsharks
' 的输出是否至少有 $thold
行。你可以像
a && echo "tsharks printed less than $thold lines"
结果
这些是我系统上的结果:
a: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.8s, 1e+09=8.9s,
b: 1e+06=0.0s, 1e+07=0.1s, 1e+08=0.9s, 1e+09=8.4s,
c: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=16.1s,
d: 1e+06=0.0s, 1e+07=0.2s, 1e+08=1.6s, 1e+09=15.7s,
e: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=83.2s,
f: 1e+06=0.1s, 1e+07=0.8s, 1e+08=8.2s, 1e+09=84.6s,
g: 1e+06=0.0s, 1e+07=0.3s, 1e+08=3.0s, 1e+09=31.6s,
h: 1e+06=7.7s, 1e+07=90.0s, ... (manually aborted)
b: ... 1e+08=0.9s ...
表示方法 b
花了 0.9 秒才发现 seq 1000000000
的输出至少有 1e+08
(= 100'000'000) 行.
结论
从这个答案中提出的方法来看,b
显然是最快的。但是,实际结果可能因系统而异(head
、grep
、...有不同的实现和版本)以及您的实际用例。我建议使用您的实际数据进行基准测试(即,将 gen()
中的 seq
替换为您的 tsharks output lines
并将 thold
设置为任何实际使用的值)。
如果您需要更快的方法,您可以尝试使用 stdbuf
和 LC_ALL=C
。