如何計算管道中間的線數
我想計算管道中的行數,然後根據結果繼續管道。
我試過
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
似乎我無法從命令替換內部重定向標準錯誤,因為這也不起作用(在 bash 中)。它列印所有三行:
x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a
在 zsh 中它只列印兩行。
但是在兩個 shell 中,變數 x 在管道之後都沒有設置,甚至在管道的後半部分也沒有設置。
我該怎麼做才能計算管道中的行數,然後根據該數字採取行動?我想避免使用臨時文件。
這個評論是真的:
管道的每個部分都獨立於同一管道的其他部分啟動。這意味著
$x
如果它設置在其他階段之一,則無法在管道中間使用。這並不意味著你不能做任何事情。管道可能被認為是主要的數據通道,但程序仍然可以使用側通道進行通信:文件、命名 fifo 或其他任何東西(儘管有時您需要格外小心,不要讓它們阻塞)。
您想計算行數並稍後有條件地處理整個數據流。這意味著您需要到達流的末尾,然後才能傳遞整個流。所以你需要以某種方式保存整個流。臨時文件看起來像是一種理智的方法。您應該將管道分成至少兩部分。第一部分應將數據保存在文件中;然後應該計算行數(我認為這個任務可能屬於第一部分);然後最後一部分應該得到數字,從頭開始讀取文件以接收數據,並採取相應的行動。
如果您真的想避免使用臨時文件,那麼您的管道的某些部分應該以某種方式表現得像
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
程式碼可以簡化,請在評論中給我提示。