muffel
muffel

Reputation: 7370

How to repeatedly pipe input between commands until condition on output applies?

I want to create a bash script that repeatedly pipes output between two processes, until the output contains some special characters.

Consider these two Python scripts:

script1.py

from sys import stdin
for line in stdin.readline():
    if line.isdigit():
        print int(line) + 3

script2.py

from sys import stdin
for line in stdin.readline():
    if line.isdigit():
        print int(line) - 1

and the following execution

$ echo 0|python script1.py|python script2.py|python script1.py|python script2.py 
4

Can a Bash script be created, that executes the two scripts and passes the output from one as input to the other, until the output of script1 reaches some certain number?

(I don't want to modify the Python scripts, I'm not actually using them, they were just created in order to demonstrate what I want to achieve).

Upvotes: 1

Views: 131

Answers (2)

konsolebox
konsolebox

Reputation: 75488

A safer approach that could prevent fork bomb:

function piper {
    read
    if [[ $REPLY == 4 ]]; then
        echo 4
    elif [[ $REPLY =~ ^[[:digit:]]+$ && $REPLY -lt 4 ]]; then
        echo "$REPLY" | python script1.py | python script2.py | piper
    fi
}

echo 0 | piper

Upvotes: 2

gniourf_gniourf
gniourf_gniourf

Reputation: 46823

Assuming that your python script script2.py only outputs one number on one line, and you want to execute python script1.py | python script2.py until the output number is 4, here's a recursive way. It uses recursion so you might hit a stackoverflow if there are too many calls:

#!/bin/bash

apply_until4() {
   local l
   read l < <(python script1.py | python script2.py)
   if [[ $l = 4 ]]; then
      echo "$l"
      return
   fi
   echo "$l" | apply_until4
}

echo "0" | apply_until4

or (with the same logic but slightly different implementation—not pure Bash anymore because of the cat, though this could be changed):

#!/bin/bash

apply_until4() {
   local l next=apply_until4
   read l < <(python script1.py | python script2.py)
   [[ $l = 4 ]] && next=cat
   echo "$l" | $next
}

echo "0" | apply_until4

Another possibility without recursion:

#!/bin/bash

apply_until4() {
   local l
   read l
   until [[ $l = 4 ]]; do
      read l < <(echo "$l" | python script1.py | python script2.py)
   done
   echo "$l"
}

echo "0" | apply_until4

  • In both methods, the idea is to read the output of the combo python script1.py | python script2.py and if the output is not 4, feed it to the combo again.
  • The

    read l < <(python script.py | python script2.py)
    

    statement can be changed to

    l=$(python script.py | python script2.py)
    

    and, similarly, the

    read l < <(echo "$l" | python script1.py | python script2.py)
    

    statement can be changed to

    l=$(echo "$l" | python script1.py | python script2.py)
    

    with the difference that the read will discard all leading and trailing spaces, if any.

Upvotes: 1

Related Questions