Multiprocess with shared variable in bash

593 Views Asked by At

I'm trying to achieve a dynamic progress bar in bash script, the kind we see when installing new packages. In order to do this, a randomtask would call a progressbar script as a background task and feed it with some integer values.

The first script uses a pipe to feed the second.

#!/bin/bash
# randomtask

pbar_x=0            # percentage of progress
pbar_xmax=100

while [[ $pbar_x != $pbar_xmax ]]; do
    echo "$pbar_x"
    sleep 1
done | ./progressbar &

# do things
(( pbar_x++ ))

# when task is done
(( pbar_x = pbar_xmax ))

Hence, the second script needs to constantly receive the integer, and print it.

#!/bin/bash
# progressbar

while [ 1 ]; do
    read x
    echo "progress: $x%"
done

But here, the second script doesn't receive the values as they are updated. What did I do wrong ?

2

There are 2 best solutions below

0
On BEST ANSWER

I'm on WSL, which means I can't use mkfifo. And coproc seemed to perfectly answer my need, so I searched and eventually found this: coproc usage with exemples [bash-hackers wiki].

We start the process with coproc and redirect its output to stdout:

{ coproc PBAR { ./progressbar; } >&3; } 3>&1

Then we can access its in and out via file descriptors ${PBAR[0]}(output) and ${PBAR[1]}(input)

    echo "$pbar_x" >&"${PBAR[1]}"

randomtask

#!/bin/bash

pbar_x=0            # percentage of progress
pbar_xmax=100

{ coproc PBAR { ./progressbar; } >&3; } 3>&1

while (( pbar_x <= 10)); do
    echo $(( pbar_x++ )) >&"${PBAR[1]}"
    sleep 1
done

# do things
echo $(( pbar_x++ )) >&"${PBAR[1]}"

# when task is done
echo $(( pbar_x = pbar_xmax )) >&"${PBAR[1]}"

progressbar

#!/bin/bash

while read x; do
    echo "progress: $x%"
done

Please note that :

The coproc keyword is not specified by POSIX(R).

The coproc keyword appeared in Bash version 4.0-alpha

3
On

That can't work, the while loop is running in a subprocess, changes in the main program will not affect it in any way.

There are several IPC mechanisms, here I use a named pipe (FIFO):

pbar_x=0            # percentage of progress
pbar_xmax=100
pipename="mypipe"

# Create the pipe 
mkfifo "$pipename"

# progressbar will block waiting on input
./progressbar < "$pipename" &

while (( pbar_x != pbar_xmax )); do
     #do things
     (( pbar_x++ ))
     echo "$pbar_x"
     sleep 1
     # when task is done
     #(( pbar_x = pbar_xmax ))
done > "$pipename"

rm "$pipename"

I also modified progressbar:

# This exits the loop when the pipe is closed
while read x
do
    echo "progress: $x%"
done

With a third script you could use process substitution instead.