Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a pipe loop in bash

Tags:

Assume that I have programs P0, P1, ...P(n-1) for some n > 0. How can I easily redirect the output of program Pi to program P(i+1 mod n) for all i (0 <= i < n)?

For example, let's say I have a program square, which repeatedly reads a number and than prints the square of that number, and a program calc, which sometimes prints a number after which it expects to be able to read the square of it. How do I connect these programs such that whenever calc prints a number, square squares it returns it to calc?

Edit: I should probably clarify what I mean with "easily". The named pipe/fifo solution is one that indeed works (and I have used in the past), but it actually requires quite a bit of work to do properly if you compare it with using a bash pipe. (You need to get a not yet existing filename, make a pipe with that name, run the "pipe loop", clean up the named pipe.) Imagine you could no longer write prog1 | prog2 and would always have to use named pipes to connect programs.

I'm looking for something that is almost as easy as writing a "normal" pipe. For instance something like { prog1 | prog2 } >&0 would be great.

like image 508
mweerden Avatar asked Sep 02 '08 18:09

mweerden


People also ask

What is the pipe command in bash?

A pipe in Bash takes the standard output of one process and passes it as standard input into another process. Bash scripts support positional arguments that can be passed in at the command line. Guiding principle #1: Commands executed in Bash receive their standard input from the process that starts them.

Can you use pipe in shell script?

The pipe character | is used to connect the output from one command to the input of another. > is used to redirect standard output to a file. Try it in the shell-lesson-data/exercise-data/proteins directory!

How do you end a while loop in bash?

Breaking from a while LoopUse the break statement to exit a while loop when a particular condition realizes. The following script uses a break inside a while loop: #!/bin/bash i=0 while [[ $i -lt 11 ]] do if [[ "$i" == '2' ]] then echo "Number $i!" break fi echo $i ((i++)) done echo "Done!"


1 Answers

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.

like image 135
mweerden Avatar answered Oct 11 '22 11:10

mweerden