How to make a pipe loop in bash

前端 未结 7 1509
半阙折子戏
半阙折子戏 2020-12-05 03:25

Assume that I have programs P0, P1, ...P(n-1) for some n > 0. How can I easily redirect the output of program Pi

相关标签:
7条回答
  • 2020-12-05 03:37

    I doubt sh/bash can do it. ZSH would be a better bet, with its MULTIOS and coproc features.

    0 讨论(0)
  • 2020-12-05 03:38

    My solutions uses pipexec (Most of the function implementation comes from your answer):

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

    The command

    pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \
        "{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}"
    

    The output (same as in your answer)

    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
    

    Comment: pipexec was designed to start processes and build arbitrary pipes in between. Because bash functions cannot be handled as processes, there is the need to have the functions in separate files and use a separate bash.

    0 讨论(0)
  • 2020-12-05 03:47

    Named pipes.

    Create a series of fifos, using mkfifo

    i.e fifo0, fifo1

    Then attach each process in term to the pipes you want:

    processn < fifo(n-1) > fifon

    0 讨论(0)
  • 2020-12-05 03:49

    This is a very interesting question. I (vaguely) remember an assignment very similar in college 17 years ago. We had to create an array of pipes, where our code would get filehandles for the input/output of each pipe. Then the code would fork and close the unused filehandles.

    I'm thinking you could do something similar with named pipes in bash. Use mknod or mkfifo to create a set of pipes with unique names you can reference then fork your program.

    0 讨论(0)
  • 2020-12-05 03:51

    After spending quite some time yesterday trying to redirect stdout to stdin, I ended up with the following method. It isn't really nice, but I think I prefer it over the named pipe/fifo solution.

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

    The { ... } >/dev/fd/0 is to redirect stdout to stdin for the pipe sequence as a whole (i.e. it redirects the output of P(n-1) to the input of P0). Using >&0 or something similar does not work; this is probably because bash assumes 0 is read-only while it doesn't mind writing to /dev/fd/0.

    The initial read-pipe is necessary because without it both the input and output file descriptor are the same pts device (at least on my system) and the redirect has no effect. (The pts device doesn't work as a pipe; writing to it puts things on your screen.) By making the input of the { ... } a normal pipe, the redirect has the desired effect.

    To illustrate with my calc/square example:

    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
    

    Running the above code gives the following output:

    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
    

    Of course, this method is quite a bit of a hack. Especially the read part has an undesired side-effect: termination of the "real" pipe loop does not lead to termination of the whole. I couldn't think of anything better than read as it seems that you can only determine that the pipe loop has terminated by try to writing write something to it.

    0 讨论(0)
  • 2020-12-05 03:53

    A named pipe might do it:

    $ mkfifo outside
    $ <outside calc | square >outside &
    $ echo "1" >outside ## Trigger the loop to start
    
    0 讨论(0)
提交回复
热议问题