durandaltheta durandaltheta - 2 months ago 7
Linux Question

In bash how to parse and/or redirect stdout for an arbitrary program I am backgrounding

In a previous question I asked how to write to a program's stdin. This question builds off of that one. Now I am asking "how do I parse my called program's output".

The fact that I'm calling a program I can edit (Sum.sh) in this example is irrelevant to the question. I want this behavior in the event that I do not have control over the implementation of the program I am calling. I want to "replace" the physical human that a program expects and drive it via a script.

Given two scripts

Sum.sh:

#!/bin/bash

sum=0
inp=0

while true;
do
printf "Sum: %d\n" $sum
printf "Give me an integer: "
read inp
if [ "$inp" == 0 ]; then
exit
fi
sum=$((sum+inp))
done


And driver.sh:

#!/bin/bash
rm -f /tmp/sumpipe
mkfifo /tmp/sumpipe

./Sum.sh < /tmp/sumpipe &


echo 2 > /tmp/sumpipe
echo 5 > /tmp/sumpipe
echo 0 > /tmp/sumpipe

sleep 1 # this is required for some reason or the script hangs


And I run driver.sh I get the following:

$ ./driver.sh
Sum: 0
Give me an integer: Sum: 2
Give me an integer: Sum: 7
Give me an integer: $


The formatting of the terminal output is not important to this question.

What I want to do is parse said output in order to make decisions in driver.sh. In the following psuedo code of driver.sh I try to show what I want to do:

#!/bin/bash
rm -f /tmp/sumpipe
mkfifo /tmp/sumpipe

output=$(./Sum.sh < /tmp/sumpipe &)

while true; do
if [ "$output" != empty ]; then
(make some decision based on what output is and echo back to my process if necessary)
fi
done


Unfortunately,
$output
never contains anything, and I have tried many versions of abusing read and piping and none of them have worked for me in this application. How can I redirect my backgrounded program's stdout so I can parse it?




I attempted OrangesV's proposed solution (and with deference to William Pursell's suggestion about the out buffer being too empty) with the following:

#!/bin/bash
rm -f /tmp/sumpipe
mkfifo /tmp/sumpipe

while read output; do
echo "2347823" > /tmp/sumpipe
if [ -z $output ]; then
continue
else
echo "got it!"
echo $output
sleep 1
fi
done < <(./Sum.sh < /tmp/sumpipe &)


Unfortunately, "got it!" is never echoed, so it seems it didn't work.

In case it's relevant, my bash version:

$ bash --version
GNU bash, version 4.4.0(7)-rc2 (x86_64-unknown-linux-gnu)





There is now a partial solution. Diego Torres Milano's answer does work. However, my stdio is being buffered so that I won't get anything till stdio has at least 4096 bytes in it. I can force output with the following version of driver.sh:

#!/bin/bash

inpipe=/tmp/sumpipei
outpipe=/tmp/sumpipeo

rm -f $inpipe
rm -f $outpipe

mkfifo $inpipe
mkfifo $outpipe

./Sum.sh < $inpipe > $outpipe &


count=0
while true;
do
while read line
do
echo "line=$line"
done < $outpipe &
echo 2 > $inpipe
done


Unfortunately, stdbuf does not apparently work in this situation to force off buffering because, according to the GNU coreutils manual on stdbuf for
command
in
stdbuf option… command
:


command must start with the name of a program that does ... not adjust
the buffering of its standard streams (note the program tee is not in
this category).


Which is unfortunately exactly what I'm doing here.

I'm marking Diego's answer as correct, even though it doesn't work until the stdio buffer issue is fixed. I'll make question to address the stdio buffering problem. I can almost taste the prize here.

Answer

Use another pipe for the output

mkfifo /tmp/output
./Sum.sh < /tmp/sumpipe > /tmp/output &

and you can process it with

while read line
do
    echo "line=$line"
done < /tmp/output &