代码之家  ›  专栏  ›  技术社区  ›  mweerden

如何在bash中创建管道循环

  •  20
  • mweerden  · 技术社区  · 16 年前

    假设我有程序 P0 , P1 ,… P(n-1) 对于一些 n > 0 . 如何轻松重定向程序输出 Pi 编程 P(i+1 mod n) 为了所有 i ( 0 <= i < n )?

    例如,假设我有一个程序 square ,它反复读取一个数字,然后打印该数字的平方,然后 calc ,它有时会打印一个数字,然后期望能够读取其平方。如何连接这些程序以便 打印一个号码, 广场 使其返回到 ?

    编辑:我应该澄清一下我说的“轻松”是什么意思。命名管道/fifo解决方案确实有效(我在过去也曾使用过),但如果将其与使用bash管道进行比较,实际上需要做大量工作才能正常工作。(您需要获取一个尚未存在的文件名,使用该名称创建一个管道,运行“pipe loop”,清理命名管道。)假设您无法再编写 prog1 | prog2 并且必须始终使用命名管道来连接程序。

    我在找一种几乎和写“普通”烟斗一样简单的东西。例如 { prog1 | prog2 } >&0 太好了。

    7 回复  |  直到 10 年前
        1
  •  24
  •   mweerden    16 年前

    在昨天花了相当长的时间试图改变方向之后 stdout stdin ,我最终采用了以下方法。它不是很好,但我想我更喜欢它而不是命名管道/先进先出解决方案。

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

    这个 { ... } >/dev/fd/0 将整个管道序列的stdout重定向到stdin(即将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
    

    当然,这种方法有点老套。尤其是 阅读 部分具有不期望的副作用:“真实”管道回路的终止不会导致整体的终止。我想不出比这更好的了 阅读 似乎只能通过尝试写入内容来确定管道循环已终止。

        2
  •  15
  •   Douglas Leeder    16 年前

    命名管道可能会这样做:

    $ mkfifo outside
    $ <outside calc | square >outside &
    $ echo "1" >outside ## Trigger the loop to start
    
        3
  •  5
  •   Mark Witczak    16 年前

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

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

        4
  •  3
  •   Andreas Florath    10 年前

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

    平方英寸

    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 $@
    

    Cal.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。

        5
  •  1
  •   1729    16 年前

    命名管道。

    使用mkfifo创建一系列fifo

    即fifo0,fifo1

    然后根据需要将每个进程附加到管道:

    处理先进先出(n-1)>先进先出

        6
  •  -1
  •   Penz    16 年前

    我怀疑sh/bash能做到。 zsh的multios和coproc特性将是更好的选择。

        7
  •  -2
  •   Fritz G. Mehner    16 年前

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