Search code examples
linuxbashmacosshellpv

Progress bar + ETA on same line with pv command


Intro

I'm using the pv command in a pipe to show a progress bar. I tried it with a simple counter:

for (( i = 1 ; i <= 100 ; i++ )); do sleep 1; echo $i; done | pv --progress --line-mode --size 100 --eta --timer

This works fine, but I'd like the progress bar to show on the same line. This answer explains how to do that.

So I tried this:

for (( i = 1 ; i <= 100 ; i++ )); do sleep 1; echo $i; done | >&2 echo -en "\r"; pv --progress --line-mode --size 100 --eta --timer

It stays on one line, but now it doesn't update the ETA anymore.

Question

How can I get the ETA to update too?

Update

Now that iBug answered the question from the previous section, I realized I had one more requirement that's relevant: the stdout needs to be preserved so it can be used in the next pipe. In my specific case I need to write the result to a file (i.e. > some-file.txt)


Solution

  • You're typing the wrong command.

    for (( i = 1 ; i <= 100 ; i++ )); do sleep 1; echo $i; done | >&2 echo -en "\r"; pv --progress --line-mode --size 100 --eta --timer
                                                                                   ^
    

    There's a semicolon before pv, so you're actually running it on stdin/stdout, which is your terminal. You should group the extra echo and pv to let it read from the for loop:

    for (( i = 1 ; i <= 100 ; i++ )); do sleep 1; echo $i; done | (>&2 echo -en "\r"; pv --progress --line-mode --size 100 --eta --timer)
    

    Why this isn't the case for the first command? It is because the whole for do done clause is treated as a single command, so its result correctly gets piped to pv. In the second command, however, the result is piped to echo. You know, that echo doesn't read anything from stdin.

    Because pv directs stdin to stdout, the numbers also gets output to the terminal, which mixes up with the indication to stderr. To suppress the nornal output, redirect it to /dev/null, so the final command is

    for (( i = 1 ; i <= 100 ; i++ )); do sleep 1; echo $i; done | (>&2 echo -en "\r"; pv --progress --line-mode --size 100 --eta --timer) > /dev/null
    

    If you want to redirect the output to a file, just change /dev/null at the end of the command.