Search code examples
bashtqdm

How to properly use tqdm progress bars in bash scripts that output messages


I am wondering how to use tqdm with a script that also prints other messages besides the ones dedicated to monitor progression.

For example, suppose a loop like this, where three messages are written to stdout for each loop cycle:

for i in $(seq 10);
do
    # This is the message used to control progress
    echo "progress"

    # Other messages, invalidate progess bar 
    echo "b"
    echo "c"

    # Simulate work
    sleep 0.5
done | tqdm --total 10 --null;

If executed, this script shows a correct progess bar until 10 messages are written to stdout, then it goes back to the minimal progress bar as if it was not given the total, something like this:

30it [00:04,  6.01it/s]

This is because the total was set to 10, but more than 10 messages (30, actually) are sent through the pipe.

What I ended up writing is something like this, with the help of some other posts in StackOverflow and Unix & Linux Stack Exchange:

# Based on https://unix.stackexchange.com/a/537435/434897
{
for i in $(seq 10);
do
    # This is the message used to control progress. sent to fd 3, only captured by tqdm
    echo "progress" >&3

    # Other messages, would invalidate progress report if sent directly to tqdm
    echo "b"
    echo "c"

    # Simulate work
    sleep 0.1
done 3>&1 >&4 | tqdm --total 10 --null;
} 4>&1

In summary, in the loop I write the progress messages to file descriptor 3 (echo "progress" >&3), all the other messages are written to stdout. Then, for the loop, I redirect 3 to stdout (3>&1), so that I can pipe those messages to tqdm, and redirect stdout to fd 4 (>%4), to avoid other messages being sent to the pipe and to tqdm. Finally, I redirect fd 4 to 1 (4>&1) for the whole loop + tqdm compound, so that the output is actually written to stdout and printed to the terminal.

Is there a more straightforward way to accomplish this behavior?


Solution

  • You can use a process substitution instead:

    for i in $(seq 10);
    do
        # This is the message used to control progress
        echo "progress" >&3
    
        # Other messages, invalidate progess bar 
        echo "b"
        echo "c"
    
        # Simulate work
        sleep 0.5
    done 3> >(tqdm --total 10 --null)