durandaltheta durandaltheta - 1 year ago 61
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




while true;
printf "Sum: %d\n" $sum
printf "Give me an integer: "
read inp
if [ "$inp" == 0 ]; then

And driver.sh:

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:

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)

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:

rm -f /tmp/sumpipe
mkfifo /tmp/sumpipe

while read output; do
echo "2347823" > /tmp/sumpipe
if [ -z $output ]; then
echo "got it!"
echo $output
sleep 1
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:



rm -f $inpipe
rm -f $outpipe

mkfifo $inpipe
mkfifo $outpipe

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

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

Unfortunately, stdbuf does not apparently work in this situation to force off buffering because, according to the GNU coreutils manual on stdbuf for
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 Source

Use another pipe for the output

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

and you can process it with

while read line
    echo "line=$line"
done < /tmp/output &
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download