如何在bash中制作管道循环

时间:2020-03-05 18:46:38  来源:igfitidea点击:

假设我有一些n> 0的程序P0P1,... P(n-1)`。我如何轻松地将所有i的程序Pi的输出重定向到程序P(i + 1 mod n)的输出(0 <= i <n)?

例如,假设我有一个程序square,它会重复读取一个数字,然后打印该数字的平方,还有一个程序,它是" calc",有时会打印一个数字,之后它希望能够读取该平方它的。如何连接这些程序,以便无论何时calc打印一个数字,square平方将其返回到calc

编辑:我可能应该澄清"轻松"的意思。命名的pipe / fifo解决方案确实是可行的(并且我过去使用过),但是如果将其与bash管道进行比较,则实际上需要大量工作才能正确完成。 (我们需要获得一个尚不存在的文件名,使用该名称创建一个管道,运行"管道循环",清理命名管道。)想象我们不再可以编写prog1 |。 prog2,并且始终必须使用命名管道来连接程序。

我正在寻找几乎与编写"普通"管道一样简单的东西。例如{prog1 | prog2}>&0很棒。

解决方案

回答

这是一个非常有趣的问题。我(模糊地)记得17年前在大学里做过的一次非常相似的作业。我们必须创建一个管道数组,我们的代码将在其中获取每个管道的输入/输出的文件句柄。然后,该代码将派生并关闭未使用的文件句柄。

我想我们可以对bash中的命名管道执行类似的操作。使用mknod或者mkfifo创建一组可以引用的唯一名称的管道,然后派生程序。

回答

我怀疑sh / bash可以做到。
ZSH凭借其MULTIOS和coproc功能将是一个更好的选择。

回答

命名管道。

使用mkfifo创建一系列fifos

即fifo0,fifo1

然后在术语中将每个过程添加到所需的管道:

进程<fifo(n-1)> fifon

回答

命名管道可以做到这一点:

$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start

回答

在昨天花了相当长的时间尝试将stdout重定向到stdin之后,我得到了以下方法。确实不是很好,但是我认为我更喜欢它而不是命名的pipe / 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"部分具有不希望的副作用:"真实"管道循环的终止不会导致整体终止。我想不出什么都比"读"更好,因为我们似乎只能通过尝试向其中写一些东西来确定管道循环已终止。

回答

一个命令栈可以由任意命令数组中的字符串组成
并进行评估。下面的示例给出结果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"