![如何计算管道中间的行数](https://linux22.com/image/164587/%E5%A6%82%E4%BD%95%E8%AE%A1%E7%AE%97%E7%AE%A1%E9%81%93%E4%B8%AD%E9%97%B4%E7%9A%84%E8%A1%8C%E6%95%B0.png)
我想计算管道中的行数,然后根据结果继续管道。
我试过
x=$(printf 'faa\nbor\nbaz\n' \
| tee /dev/stderr | wc -l) 2>&1 \
| if [[ $x -ge 2 ]]; then
grep a
else
grep b
fi
但它根本不过滤(既不过滤“a”也不过滤“b”)。这是非常出乎意料的,因为至少这些工作按预期进行:
printf 'faa\nbor\nbaz\n' | if true; then grep a; else grep b; fi
printf 'faa\nbor\nbaz\n' | if false; then grep a; else grep b; fi
看来我无法从命令替换内部重定向 stderr,因为这也不起作用(在 bash 中)。它打印所有三行:
x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a
在 zsh 中它只打印两行。
但在两个 shell 中,变量 x 都不会在管道之后设置,甚至在管道的后半部分期间也不会设置。
我可以做什么来计算管道中的行数,然后根据该数字采取行动?我想避免临时文件。
答案1
这条评论是真的:
管道的每个部分独立于同一管道的其他部分启动。这意味着
$x
如果它设置在其他阶段之一,则无法在管道中间使用。
这并不意味着您不能做任何事情。管道可以被视为主要数据通道,进程仍然可以使用侧通道进行通信:文件、名为 fifos 或其他任何东西(尽管有时您需要格外小心,不要让它们阻塞)。
您想要计算行数并稍后有条件地处理整个数据流。这意味着您需要到达流的末尾,然后才传递整个流。所以你需要以某种方式保存整个流。临时文件看起来是一个明智的方法。您应该将管道分成至少两部分。第一部分应将数据保存在文件中;然后应该计算行数(我认为这个任务可能属于第一部分);那么最后一部分应该获取数字,读取文件以从头开始接收数据,并采取相应的行动。
如果您确实想避免临时文件,那么管道的某些部分应该像sponge
.为了避免旁路,行数应作为输出的第一行传递,管道的其余部分应理解此协议。
考虑这个命令:
sed '$ {=; H; g; p;}; H; d'
它将线路累积在保留空间中。如果至少有一行,则在收到最后一行后sed
打印行数,后跟空行和实际输入。
空行是不必要的,但从这个简单的代码中它“自然”地出现。我不会在 中尝试避免它sed
,而是稍后在管道中处理它(例如使用sed '2 d'
)。
用法示例:
#!/bin/sh
sed '$ {=; H; g; p;}; H; d' | sed '2 d' | {
if ! IFS= read -r nlines; then
echo "0 lines. Nothing to do." >&2
else
echo "$nlines lines. Processing accordingly." >&2
if [ "$nlines" -ge 2 ]; then
grep a
else
grep b
fi
fi
}
笔记:
IFS= read -r
是一种矫枉过正,因为第一行定义明确并且它包含唯一的数字(或者它不存在)。- 我用了
/bin/sh
。该代码也将在 Bash 中运行。 您不能假设
sed
能够保存任意数量的数据。POSIX规范说:模式和保持空间均应能够容纳至少 8192 字节。
所以限制可能只有 8192 字节。另一方面,我可以想象一个临时文件可以轻松容纳 1TB 的数据。也许不要不惜一切代价避免临时文件。
标题说“计算行数”,但您的示例尝试确定该数字是否为 2 或更多(通常为 N 或更多)。这些问题并不等同。输入第二(第 N)行后,您就知道后一个问题的答案,甚至行将无限期地出现。上面的代码无法处理不确定的输入。让我们在某种程度上修复它。
sed '
7~1 {p; d}
6 {H; g; i \
6+
p; d}
$ {=; H; g; p}
6! {H; d}
'
此命令的行为类似于前面的解决方案,除了当它到达第 6 行时,它假定(打印)行数为6+
。然后打印已经看到的行,并且以下行(如果有)一出现就被打印(cat
类似行为)。
用法示例:
#!/bin/sh
threshold=6
sed "
$((threshold+1))~1 {p; d}
$threshold {H; g; i \
$threshold+
p; d}
$ {=; H; g; p}
${threshold}! {H; d}
" | sed '2 d' | {
if ! IFS= read -r nlines; then
echo "0 lines. Nothing to do." >&2
else
echo "$nlines lines. Processing accordingly." >&2
if [ "$nlines" = "$threshold+" ]; then
grep a
else
grep b
fi
fi
}
笔记:
- 修复了“在某种程度上”,因为
sed
(无论您的情况有什么限制)的限制仍然适用。但现在sed
最多处理$threshold
行数;如果$threshold
足够低那么应该没问题。 - 示例代码仅进行测试,
$threshold+
但协议允许您区分 0、1、2、...、阈值减一和阈值或更多行。
我不太擅长sed
。如果我的sed
代码可以简化,请在评论中给我留言。
答案2
根据讨论和 Kamil 的 sed 代码,我找到了这个 awk 解决方案:
awk -v th="$threshold" '
function print_lines() { for (i in lines) print lines[i] }
NR < th { lines[NR] = $0 }
NR > th { print }
NR == th { print th; print_lines(); print }
END { if (NR < th) { print NR; print_lines(); } }' \
| if read nlines; then
if [ "$nlines" -eq "$threshold" ]; then
grep a
else
grep b
fi
fi