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
To indicate progress of activity, try the following commands:
while true; do sleep 0.25 && echo -ne "\r\\" && sleep 0.25 && echo -ne "\r|" && sleep 0.25 && echo -ne "\r/" && sleep 0.25 && echo -ne "\r-"; done;
OR
while true; do sleep 0.25 && echo -ne "\rActivity: \\" && sleep 0.25 && echo -ne "\rActivity: |" && sleep 0.25 && echo -ne "\rActivity: /" && sleep 0.25 && echo -ne "\rActivity: -"; done;
OR
while true; do sleep 0.25 && echo -ne "\r" && sleep 0.25 && echo -ne "\r>" && sleep 0.25 && echo -ne "\r>>" && sleep 0.25 && echo -ne "\r>>>"; sleep 0.25 && echo -ne "\r>>>>"; done;
OR
while true; do sleep .25 && echo -ne "\r:Active:" && sleep .25 && echo -ne "\r:aCtive:" && sleep .25 && echo -ne "\r:acTive:" && sleep .25 && echo -ne "\r:actIve:" && sleep .25 && echo -ne "\r:actiVe:" && sleep .25 && echo -ne "\r:activE:"; done;
One can use flags/variables inside the while loop to check and display the value/extent of progress.
Haven't seen anything similar and all custom functions here seem to focus on rendering alone so... my very simple POSIX compliant solution below with step by step explanations because this question isn't trivial.
Rendering the progress bar is very easy. Estimating how much of it should render is a different matter. This is how to render (animate) the progress bar - you can copy&paste this example to a file and run it:
#!/bin/sh
BAR='####################' # this is full bar, e.g. 20 chars
for i in {1..20}; do
echo -ne "\r${BAR:0:$i}" # print $i chars of $BAR from 0 position
sleep .1 # wait 100ms between "frames"
done
{1..20}
- values from 1 to 20echo -n
- print without new line at the endecho -e
- interpret special characters while printing"\r"
- carriage return, a special char to return to the beginning of the lineYou can make it render any content at any speed so this method is very universal, e.g. often used for visualization of "hacking" in silly movies, no kidding.
The meat of the problem is how to determine the $i
value, i.e. how much of the progress bar to display. In the above example I just let it increment in for
loop to illustrate the principle but a real life application would use an infinite loop and calculate the $i
variable on each iteration. To make said calculation it needs the following ingredients:
In case of cp
it needs the size of a source file and the size of the target file:
#!/bin/sh
$src=/path/to/source/file
$tgt=/path/to/target/file
cp "$src" "$tgt" & # the & forks the `cp` process so the rest
# of the code runs without waiting (async)
BAR='####################'
src_size=$(stat -c%s "$src") # how much there is to do
while true; do
tgt_size=$(stat -c%s "$tgt") # how much has been done so far
i=$(( $tgt_size * 20 / $src_size ))
echo -ne "\r${BAR:0:$i}"
if [ $tgt_size == $src_size ]; then
echo "" # add a new line at the end
break; # break the loop
fi
sleep .1
done
stat
- check file stats-c
- return formatted value%s
- total sizeIn case of operations like file unpacking, calculating the source size is slightly more difficult but still as easy as getting the size of an uncompressed file:
#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
gzip -l
- display info about zip archivetail -n1
- work with 1 line from the bottomtr -s ' '
- translate multiple spaces to one (squeeze them)cut -d' ' -f3
- cut 3rd space-delimited columnHere's the meat of the problem, though. This solution is less and less general. All calculations of the actual progress are tightly bound to the domain you're trying to visualize, is it a single file operation, a timer countdown, a rising number of files in a directory, operation on multiple files, etc., therefore, it can't be reused. The only reusable part is progress bar rendering. To reuse it you need to abstract it and save in a file (e.g. /usr/lib/progress_bar.sh
), then define functions that calculate input values specific to your domain. This is how a generalized code could look like (I also made the $BAR
dynamic because people were asking for it, the rest should be clear by now):
#!/bin/sh
BAR_length=50
BAR_character='#'
BAR=$(printf "%${BAR_length}s" | tr ' ' $BAR_character)
work_todo=$(get_work_todo) # how much there is to do
while true; do
work_done=$(get_work_done) # how much has been done so far
i=$(( $work_done * $BAR_length / $work_todo ))
echo -ne "\r${BAR:0:$i}"
if [ $work_done == $work_todo ]; then
echo ""
break;
fi
sleep .1
done
printf
- a builtin for printing stuff in a given formatprintf '%50s'
- print nothing, pad it with 50 spacestr ' ' '#'
- translate every space to hash signAnd this is how you'd use it:
#!/bin/sh
src=/path/to/source/file
tgt=/path/to/target/file
function get_work_todo() {
echo $(stat -c%s "$src")
}
function get_work_done() {
[ -e "$tgt" ] && # if target file exists
echo $(stat -c%s "$tgt") || # echo its size, else
echo 0 # echo zero
}
cp "$src" "$tgt" & # copy in the background
source /usr/lib/progress_bar.sh # execute the progress bar
Obviously it can be wrapped in a function, rewritten to work with piped streams, rewritten to other language, whatever's your poison.
Most unix commands will not give you the sort of direct feedback from which you can do this. Some will give you output on stdout or stderr that you can use.
For something like tar you could use the -v switch and pipe the output to a program that updates a small animation for each line it reads. As tar writes out a list of files it's unravelled the program can update the animation. To do a percent complete you would have to know the number of files and count the lines.
cp doesn't give this sort of output as far as I know. To monitor the progress of cp you would have to monitor the source and destination files and watch the size of the destination. You could write a small c program using the stat (2) system call to get the file size. This would read the size of the source then poll the destination file and update a % complete bar based on the size of the file written to date.
I was looking for something more sexy than the selected answer, so did my own script.
I put it on github progress-bar.sh
progress-bar() {
local duration=${1}
already_done() { for ((done=0; done<$elapsed; done++)); do printf "▇"; done }
remaining() { for ((remain=$elapsed; remain<$duration; remain++)); do printf " "; done }
percentage() { printf "| %s%%" $(( (($elapsed)*100)/($duration)*100/100 )); }
clean_line() { printf "\r"; }
for (( elapsed=1; elapsed<=$duration; elapsed++ )); do
already_done; remaining; percentage
sleep 1
clean_line
done
clean_line
}
progress-bar 100
First of all bar is not the only one pipe progress meter. The other (maybe even more known) is pv (pipe viewer).
Secondly bar and pv can be used for example like this:
$ bar file1 | wc -l
$ pv file1 | wc -l
or even:
$ tail -n 100 file1 | bar | wc -l
$ tail -n 100 file1 | pv | wc -l
one useful trick if you want to make use of bar and pv in commands that are working with files given in arguments, like e.g. copy file1 file2, is to use process substitution:
$ copy <(bar file1) file2
$ copy <(pv file1) file2
Process substitution is a bash magic thing that creates temporary fifo pipe files /dev/fd/ and connect stdout from runned process (inside parenthesis) through this pipe and copy sees it just like an ordinary file (with one exception, it can only read it forwards).
Update:
bar command itself allows also for copying. After man bar:
bar --in-file /dev/rmt/1cbn --out-file \
tape-restore.tar --size 2.4g --buffer-size 64k
But process substitution is in my opinion more generic way to do it. An it uses cp program itself.
https://github.com/extensionsapp/progre.sh
Create 40 percent progress: progreSh 40