假设某些P0
我有P1
,P(n-1)
,... n > 0
个程序。如何轻松地将所有Pi
(P(i+1 mod n)
)的计划i
输出重定向到计划0 <= i < n
?
例如,假设我有一个程序square
,它重复读取一个数字而不是打印该数字的平方,以及一个程序calc
,它有时会打印一个数字,之后会发生预期能够阅读它的正方形。我如何连接这些程序,以便calc
每当square
打印一个数字calc
方格时,它会将其返回prog1 | prog2
?
编辑:我应该用“轻松”来澄清我的意思。命名管道/ fifo解决方案确实有效(我过去曾使用过),但如果将它与使用bash管道进行比较,它实际上需要相当多的工作才能正常工作。 (您需要获取一个尚未存在的文件名,使用该名称创建一个管道,运行“管道循环”,清理命名管道。)想象一下,您不能再编写{ prog1 | prog2 } >&0
并且总是必须使用命名管道连接程序。
我正在寻找与写“普通”管道一样简单的东西。例如{{1}}之类的东西会很棒。
答案 0 :(得分:24)
昨天花了很长时间尝试将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
更好的东西,因为你似乎只能通过尝试写一些内容来确定管道循环已经终止。
答案 1 :(得分:15)
命名管道可能会这样做:
$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start
答案 2 :(得分:5)
这是一个非常有趣的问题。我(含糊地)记得17年前大学里的任务非常相似。我们必须创建一个管道数组,我们的代码将获取每个管道的输入/输出的文件句柄。然后代码将分叉并关闭未使用的文件句柄。
我想你可以用bash中的命名管道做类似的事情。使用mknod或mkfifo创建一组具有唯一名称的管道,您可以引用它们然后分叉您的程序。
答案 3 :(得分:3)
我的解决方案使用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。
答案 4 :(得分:1)
命名管道。
使用mkfifo
创建一系列fifos即fifo0,fifo1
然后将每个流程按期限附加到您想要的管道上:
processn&lt; fifo(n-1)&gt; fifon
答案 5 :(得分:-1)
我怀疑sh / bash可以做到。 凭借其MULTIOS和coproc功能,ZSH将是一个更好的选择。
答案 6 :(得分:-2)
命令堆栈可以由任意命令数组组成 并使用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"