Example of Bash Progress Bar
How many times did you feel that something was not happening? Or simply, that your wait was not controllable? Such scenario happens systematically whenever in one of the Bash scripts used to automate tasks, a wait time is not properly represented, or even displayed: processes stop their output silently, and… what is the reason? Even if it is a needed wait, the final user may not know and, for example, can think to abort the script – apparently hanging.
The solution is very simple: a progress bar to make the wait fancy, but, controllable at the same time. A naive implementation in Bash looks like the below one: ‘#’ is the symbol picked to represent any progress.
$ ./run-scenario.sh setup: install dependencies: JDK7 openjdk version "1.8.0_45" OpenJDK Runtime Environment (build 1.8.0_45-b13) setup: already installed setup: install dependencies: XTERM xterm is /usr/bin/xterm setup: already installed setup: install dependencies: TCPDUMP tcpdump is /usr/sbin/tcpdump setup: already installed setup: LIB_PATH=/tmp/dane-demonstrators xterm: cannot load font '-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1' wait: status: [#####] (100%) xterm: cannot load font '-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1' wait: status: [##########] (100%)
Hereafter, the Bash source code that implements the above progress bar. The code does not have any optimization, it is just demonstrative to let any experienced user to grasp the ideas.
#!/usr/bin/env bash # indicates 100%, as reference TOT=100 # number of elements in the progress bar ITR=10 # number of seconds to wait for SEC=120 # counters CNT=0 CUR=0 # for simplicity, adapts the elements to the number of seconds if they are a few if [ $SEC -lt $ITR ]; then ITR=$SEC fi # a bunch of math let SYM=ITR let INC=TOT/ITR let SPN=SEC/ITR # prepares body and head of a printable line HEAD='wait: status: [' BODY='' # main loop, it loops over the number of elements in the progress bar while [ $CNT -le $ITR ]; do BODY='' # defines how many elements to put into the bar POUNDS=0 while [ $POUNDS -lt $CNT ]; do BODY="$BODY#" let POUNDS=POUNDS+1 done # defines how many blank spaces to append let BLANKS=SYM-CNT while [ $BLANKS -gt 0 ]; do BODY="$BODY " let BLANKS=BLANKS-1 done # defines the line tail with the progress percentage TAIL="] ($CUR%)" echo -ne "$HEAD$BODY$TAIL\r" let CUR=CUR+INC let CNT=CNT+1 # spins before of the next update in case its needed SPIN=$SPN while [ $SPIN -gt 0 ]; do sleep 1 let SPIN=SPIN-1 done done # fixes any approximation caused by the integer arithmetic if [ $CUR -ne 100 ]; then TAIL="] (100%)" echo -ne "$HEAD$BODY$TAIL\r" fi echo ''
The main idea consists in regenerating dynamically the progress bar, displaying it on the same line will give the user a progressive effect of filling up. The first two nested loops in the main loop take care to create the ‘#’s to indicate progress, and the buffering ‘ ‘s to be appended: an example, progressively, [# ], [## ], [### ], given 5 iterations (i.e. elements of the bar) to be controlled with a percentage of increment of about 20%; last loop allows to spin before the successive increment, in case of wait time greater than 100 seconds: as per above, 120 seconds will be rendered within a progress bar of 10 elements, so each increment should be about each 12 seconds (i.e. new ‘#’s will appear each 12 seconds).
Interesting is the echoing technique. As said, the main idea is to stay inline on the same line, so to achieve that the ‘echo’ has been used combining the options ‘-n’ and ‘-e’ that allows to not go on a new line and to interprets any escaped symbol in the string.
This post aims at providing a quick solution to a recurrent problem in automation tasks. The code is for demonstrative purpose: optimizations might e done, of course.