How to add a progress bar to a shell script?

2018-12-31 14:30发布

When scripting in bash or any other shell in *NIX, while running a command that will take more than a few seconds, a progress bar is needed.

For example, copying a big file, opening a big tar file.

What ways do you recommend to add progress bars to shell scripts?

标签: bash shell zsh
30条回答
笑指拈花
2楼-- · 2018-12-31 15:06

You may also be interested in how to do a spinner:

Can I do a spinner in Bash?

Sure!

i=1
sp="/-\|"
echo -n ' '
while true
do
    printf "\b${sp:i++%${#sp}:1}"
done

Each time the loop iterates, it displays the next character in the sp string, wrapping around as it reaches the end. (i is the position of the current character to display and ${#sp} is the length of the sp string).

The \b string is replaced by a 'backspace' character. Alternatively, you could play with \r to go back to the beginning of the line.

If you want it to slow down, put a sleep command inside the loop (after the printf).

A POSIX equivalent would be:

sp='/-\|'
printf ' '
while true; do
    printf '\b%.1s' "$sp"
    sp=${sp#?}${sp%???}
done

If you already have a loop which does a lot of work, you can call the following function at the beginning of each iteration to update the spinner:

sp="/-\|"
sc=0
spin() {
   printf "\b${sp:sc++:1}"
   ((sc==${#sp})) && sc=0
}
endspin() {
   printf "\r%s\n" "$@"
}

until work_done; do
   spin
   some_work ...
done
endspin
查看更多
爱死公子算了
3楼-- · 2018-12-31 15:08
#!/bin/bash

function progress_bar() {
    bar=""
    total=10
    [[ -z $1 ]] && input=0 || input=${1}
    x="##"
   for i in `seq 1 10`; do
        if [ $i -le $input ] ;then
            bar=$bar$x
        else
            bar="$bar  "
       fi
    done
    #pct=$((200*$input/$total % 2 + 100*$input/$total))
    pct=$(($input*10))
    echo -ne "Progress : [ ${bar} ] (${pct}%) \r"    
    sleep 1
    if [ $input -eq 10 ] ;then
        echo -ne '\n'
    fi

}

could create a function that draws this on a scale say 1-10 for the number of bars :

progress_bar 1
echo "doing something ..."
progress_bar 2
echo "doing something ..."
progress_bar 3
echo "doing something ..."
progress_bar 8
echo "doing something ..."
progress_bar 10
查看更多
笑指拈花
4楼-- · 2018-12-31 15:08

This lets you visualize that a command is still executing:

while :;do echo -n .;sleep 1;done &
trap "kill $!" EXIT  #Die with parent if we die prematurely
tar zxf packages.tar.gz; # or any other command here
kill $! && trap " " EXIT #Kill the loop and unset the trap or else the pid might get reassigned and we might end up killing a completely different process

This will create an infinite while loop that executes in the background and echoes a "." every second. This will display . in the shell. Run the tar command or any a command you want. When that command finishes executing then kill the last job running in the background - which is the infinite while loop.

查看更多
孤独总比滥情好
5楼-- · 2018-12-31 15:09

Many answers describe writing your own commands for printing out '\r' + $some_sort_of_progress_msg. The problem sometimes is that printing out hundreds of these updates per second will slow down the process.

However, if any of your processes produce output (eg 7z a -r newZipFile myFolder will output each filename as it compresses it) then a simpler, fast, painless and customisable solution exists.

Install the python module tqdm.

$ sudo pip install tqdm
$ # now have fun
$ 7z a -r -bd newZipFile myFolder | tqdm >> /dev/null
$ # if we know the expected total, we can have a bar!
$ 7z a -r -bd newZipFile myFolder | grep -o Compressing | tqdm --total $(find myFolder -type f | wc -l) >> /dev/null

Help: tqdm -h. An example using more options:

$ find / -name '*.py' -exec cat \{} \; | tqdm --unit loc --unit_scale True | wc -l

As a bonus you can also use tqdm to wrap iterables in python code.

https://github.com/tqdm/tqdm/blob/master/README.rst#module

查看更多
荒废的爱情
6楼-- · 2018-12-31 15:13

Here is how it might look

Uploading a file

[##################################################] 100% (137921 / 137921 bytes)

Waiting for a job to complete

[#########################                         ] 50% (15 / 30 seconds)

Simple function that implements it

You can just copy-paste it to your script. It does not require anything else to work.

PROGRESS_BAR_WIDTH=50  # progress bar length in characters

draw_progress_bar() {
  # Arguments: current value, max value, unit of measurement (optional)
  local __value=$1
  local __max=$2
  local __unit=${3:-""}  # if unit is not supplied, do not display it

  # Calculate percentage
  if (( $__max < 1 )); then __max=1; fi  # anti zero division protection
  local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))

  # Rescale the bar according to the progress bar width
  local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))

  # Draw progress bar
  printf "["
  for b in $(seq 1 $__num_bar); do printf "#"; done
  for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done
  printf "] $__percentage%% ($__value / $__max $__unit)\r"
}

Usage example

Here, we upload a file and redraw the progress bar at each iteration. It does not matter what job is actually performed as long as we can get 2 values: max value and current value.

In the example below the max value is file_size and the current value is supplied by some function and is called uploaded_bytes.

# Uploading a file
file_size=137921

while true; do
  # Get current value of uploaded bytes
  uploaded_bytes=$(some_function_that_reports_progress)

  # Draw a progress bar
  draw_progress_bar $uploaded_bytes $file_size "bytes"

  # Check if we reached 100%
  if [ $uploaded_bytes == $file_size ]; then break; fi
  sleep 1  # Wait before redrawing
done
# Go to the newline at the end of upload
printf "\n"
查看更多
只靠听说
7楼-- · 2018-12-31 15:15

GNU tar has a useful option which gives a functionality of a simple progress bar.

(...) Another available checkpoint action is ‘dot’ (or ‘.’). It instructs tar to print a single dot on the standard listing stream, e.g.:

$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...

The same effect may be obtained by:

$ tar -c --checkpoint=.1000 /var
查看更多
登录 后发表回答