How to add a progress bar to a shell script?

后端 未结 30 2219
情歌与酒
情歌与酒 2020-11-22 05:48

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 b

相关标签:
30条回答
  • 2020-11-22 06:33

    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
    
    0 讨论(0)
  • 2020-11-22 06:33

    A simpler method that works on my system using the pipeview ( pv ) utility.

    srcdir=$1
    outfile=$2
    
    
    tar -Ocf - $srcdir | pv -i 1 -w 50 -berps `du -bs $srcdir | awk '{print $1}'` | 7za a -si $outfile
    
    0 讨论(0)
  • 2020-11-22 06:33

    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.

    0 讨论(0)
  • 2020-11-22 06:33

    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"
    
    0 讨论(0)
  • 2020-11-22 06:34

    You can implement this by overwriting a line. Use \r to go back to the beginning of the line without writing \n to the terminal.

    Write \n when you're done to advance the line.

    Use echo -ne to:

    1. not print \n and
    2. to recognize escape sequences like \r.

    Here's a demo:

    echo -ne '#####                     (33%)\r'
    sleep 1
    echo -ne '#############             (66%)\r'
    sleep 1
    echo -ne '#######################   (100%)\r'
    echo -ne '\n'
    

    In a comment below, puk mentions this "fails" if you start with a long line and then want to write a short line: In this case, you'll need to overwrite the length of the long line (e.g., with spaces).

    0 讨论(0)
  • 2020-11-22 06:35

    In case you have to show a temporal progress bar (by knowing in advance the showing time), you can use Python as follows:

    #!/bin/python
    from time import sleep
    import sys
    
    if len(sys.argv) != 3:
        print "Usage:", sys.argv[0], "<total_time>", "<progressbar_size>"
        exit()
    
    TOTTIME=float(sys.argv[1])
    BARSIZE=float(sys.argv[2])
    
    PERCRATE=100.0/TOTTIME
    BARRATE=BARSIZE/TOTTIME
    
    for i in range(int(TOTTIME)+1):
        sys.stdout.write('\r')
        s = "[%-"+str(int(BARSIZE))+"s] %d%% "
        sys.stdout.write(s % ('='*int(BARRATE*i), int(PERCRATE*i)))
        sys.stdout.flush()
        SLEEPTIME = 1.0
        if i == int(TOTTIME): SLEEPTIME = 0.1
        sleep(SLEEPTIME)
    print ""
    

    Then, assuming you saved the Python script as progressbar.py, it's possible to show the progress bar from your bash script by running the following command:

    python progressbar.py 10 50
    

    It would show a progress bar sized 50 characters and "running" for 10 seconds.

    0 讨论(0)
提交回复
热议问题