[英]How to make a pipe loop in bash
假設對於某些n > 0
,我有程序P0
, P1
,... P(n-1)
。 如何輕松地將程序Pi
的輸出重定向到所有i
程序P(i+1 mod n)
( 0 <= i < n
)?
例如,假設我有一個程序square
,它重復讀取一個數字而不是打印該數字的正方形,以及一個程序calc
,它有時打印一個數字,之后它希望能夠讀取它的平方。 我如何連接這些程序,以便每當calc
打印一個數字, square
正方形,它返回到calc
?
編輯:我應該用“輕松”來澄清我的意思。 命名管道/ fifo解決方案確實有效(我過去曾使用過),但如果將它與使用bash管道進行比較,它實際上需要相當多的工作才能正常工作。 (您需要獲取一個尚未存在的文件名,使用該名稱創建一個管道,運行“管道循環”,清理命名管道。)想象一下,您無法再編寫prog1 | prog2
prog1 | prog2
並且總是必須使用命名管道來連接程序。
我正在尋找像寫“普通”管道一樣簡單的東西。 例如{ prog1 | prog2 } >&0
{ prog1 | prog2 } >&0
會很棒。
在昨天花了很長時間試圖將stdout
重定向到stdin
,我最終得到了以下方法。 它不是很好,但我認為我比命名管道/ fifo解決方案更喜歡它。
read | { P0 | ... | P(n-1); } >/dev/fd/0
{ ... } >/dev/fd/0
是將stdout重定向到stdin作為整個管道序列(即它將P(n-1)的輸出重定向到P0的輸入)。 使用>&0
或類似的東西不起作用; 這可能是因為bash假定0
是只讀的,而它不介意寫入/dev/fd/0
。
初始read
管道是必要的,因為如果沒有它,輸入和輸出文件描述符都是相同的pts設備(至少在我的系統上)並且重定向沒有效果。 (pts設備不能用作管道;寫入它會將內容放在屏幕上。)通過輸入{ ... }
正常管道,重定向具有所需的效果。
用我的calc
/ square
示例來說明:
function calc() {
# calculate sum of squares of numbers 0,..,10
sum=0
for ((i=0; i<10; i++)); do
echo $i # "request" the square of i
read ii # read the square of i
echo "got $ii" >&2 # debug message
let sum=$sum+$ii
done
echo "sum $sum" >&2 # output result to stderr
}
function square() {
# square numbers
read j # receive first "request"
while [ "$j" != "" ]; do
let jj=$j*$j
echo "square($j) = $jj" >&2 # debug message
echo $jj # send square
read j # receive next "request"
done
}
read | { calc | square; } >/dev/fd/0
運行上面的代碼給出以下輸出:
square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285
當然,這種方法相當有點黑客。 尤其是read
部件具有不希望的副作用:“實際”管道環路的終止不會導致整體的終止。 我想不出比read
更好的東西,因為看起來你只能通過嘗試寫一些東西來確定管道循環已經終止。
命名管道可能會這樣做:
$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start
這是一個非常有趣的問題。 我(含糊地)記得17年前大學里的任務非常相似。 我們必須創建一個管道數組,我們的代碼將獲取每個管道的輸入/輸出的文件句柄。 然后代碼將分叉並關閉未使用的文件句柄。
我想你可以用bash中的命名管道做類似的事情。 使用mknod或mkfifo創建一組具有唯一名稱的管道,您可以引用它們然后分叉您的程序。
我的解決方案使用pipexec (大多數函數實現來自你的答案):
square.sh
function square() {
# square numbers
read j # receive first "request"
while [ "$j" != "" ]; do
let jj=$j*$j
echo "square($j) = $jj" >&2 # debug message
echo $jj # send square
read j # receive next "request"
done
}
square $@
calc.sh
function calc() {
# calculate sum of squares of numbers 0,..,10
sum=0
for ((i=0; i<10; i++)); do
echo $i # "request" the square of i
read ii # read the square of i
echo "got $ii" >&2 # debug message
let sum=$sum+$ii
done
echo "sum $sum" >&2 # output result to stderr
}
calc $@
命令
pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \
"{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}"
輸出(與答案相同)
square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285
注釋:pipexec旨在啟動進程並在其間構建任意管道。 由於bash函數不能作為進程處理,因此需要將函數放在單獨的文件中並使用單獨的bash。
命名管道。
使用mkfifo創建一系列的fifos
即fifo0,fifo1
然后將每個流程附加到您想要的管道:
processn <fifo(n-1)> fifon
我懷疑sh / bash可以做到。 憑借其MULTIOS和coproc功能,ZSH將是更好的選擇。
命令堆棧可以由任意命令數組組成一個字符串,並使用eval進行評估。 以下示例給出結果65536。
function square ()
{
read n
echo $((n*n))
} # ---------- end of function square ----------
declare -a commands=( 'echo 4' 'square' 'square' 'square' )
#-------------------------------------------------------------------------------
# build the command stack using pipes
#-------------------------------------------------------------------------------
declare stack=${commands[0]}
for (( COUNTER=1; COUNTER<${#commands[@]}; COUNTER++ )); do
stack="${stack} | ${commands[${COUNTER}]}"
done
#-------------------------------------------------------------------------------
# run the command stack
#-------------------------------------------------------------------------------
eval "$stack"
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.