23

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

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

编辑:我可能应该澄清“容易”的意思。命名管道/fifo 解决方案确实有效(我过去曾使用过),但如果将其与使用 bash 管道进行比较,它实际上需要相当多的工作才能正确完成。(您需要获取一个尚不存在的文件名,使用该名称创建一个管道,运行“管道循环”,清理命名管道。)想象一下,您不能再编写prog1 | prog2并且总是必须使用命名管道来连接程序。

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

4

7 回答 7

27

在昨天花了很长时间尝试重定向stdoutstdin之后,我最终采用了以下方法。这不是很好,但我认为我更喜欢它而不是命名管道/fifo 解决方案。

read | { P0 | ... | P(n-1); } >/dev/fd/0

是将管道序列作为一个整体的{ ... } >/dev/fd/0标准输出重定向到标准输入(即,它将 P(n-1) 的输出重定向到 P0 的输入)。使用>&0或类似的东西不起作用;这可能是因为 bash 假定0它是只读的,而它不介意写入/dev/fd/0.

初始read-pipe 是必要的,因为没有它,输入和输出文件描述符都是相同的 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

当然,这种方法有点hack。特别是该read部分具有不希望的副作用:“真实”管道循环的终止不会导致整体终止。我想不出比read您似乎只能通过尝试向其写入内容来确定管道循环已终止更好的事情了。

于 2008-09-04T08:22:01.790 回答
15

命名管道可能会这样做:

$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start
于 2008-09-02T20:57:53.503 回答
5

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

我想你可以在 bash 中对命名管道做类似的事情。使用 mknod 或 mkfifo 创建一组具有唯一名称的管道,您可以引用然后分叉您的程序。

于 2008-09-02T19:16:44.993 回答
3

我的解决方案使用pipexec(大部分功能实现来自您的回答):

方格.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 $@

计算.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。

于 2015-03-14T20:30:14.617 回答
1

命名管道。

创建一系列fifo,使用mkfifo

即fifo0,fifo1

然后将每个进程按术语附加到您想要的管道:

进程n < fifo(n-1) > fifo

于 2008-09-02T20:57:24.533 回答
-1

我怀疑 sh/bash 可以做到。ZSH 将是一个更好的选择,它具有 MULTIOS 和 coproc 功能。

于 2008-09-02T20:31:44.220 回答
-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" 
于 2009-01-21T09:56:37.260 回答