Reputation: 21386
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?
Upvotes: 559
Views: 553924
Reputation: 70957
Sorry for this not so short answer. In this answer I will use integer
to render floating point, UTF-8
fonts for rendering progress bar more finely, and parallelise
another task (sha1sum
) in order to follow his progression, all of this with minimal resource footprint using pure bash and no forks.
For impatiens: Please test code (copy/paste in a new terminal window) at Now do it! (in the middle), with
- either: Last animated demo (near end of this.),
- either Practical sample (at end).
All demos here use read -t <float seconds> && break
instead of sleep
. So all loop could be nicely stopped by hitting Return key.
Yet Another Bash Progress Bar...
As there is already a lot of answer here, I want to add some hints about performances and precision.
Because a progress bar are intented to run while other process are working, this must be a nice process...
So avoid using forks when not needed. Sample: instead of
mysmiley=$(printf '%b' \\U1F60E)
Use
printf -v mysmiley '%b' \\U1F60E
Explanation: When you run var=$(command)
, you initiate a new process to execute command
and send his output to variable $var
once terminated. This is very resource expensive. Please compare:
TIMEFORMAT="%R"
time for ((i=2500;i--;)){ mysmiley=$(printf '%b' \\U1F60E);}
2.292
time for ((i=2500;i--;)){ printf -v mysmiley '%b' \\U1F60E;}
0.017
bc -l <<<'2.292/.017'
134.82352941176470588235
On my host, same work of assigning $mysmiley
(just 2500 time), seem ~135x slower / more expensive by using fork than by using built-in printf -v
.
Then
echo $mysmiley
๐
So your function
have to not print (or output) anything. Your function have to attribute his answer to a variable.
Here is a very small and quick function to compute percents from integers, with integer and answer a pseudo floating point number:
percent(){
local p=00$(($1*100000/$2))
printf -v "$3" %.2f ${p::-3}.${p: -3}
}
Usage:
# percent <integer to compare> <reference integer> <variable name>
percent 33333 50000 testvar
printf '%8s%%\n' "$testvar"
66.67%
โ โ โ โ โ โ โ โ
To render this characters using bash, you could:
printf -v chars '\\U258%X ' {15..8}
printf '%b\n' "$chars"
โ โ โ โ โ โ โ โ
or
printf %b\ \\U258{{f..a},9,8}
โ โ โ โ โ โ โ โ
Then we have to use 8x string width
as graphic width
.
This function is named percentBar
because it render a bar from argument submited in percents (floating):
percentBar () {
local prct totlen=$((8*$2)) lastchar barstring blankstring;
printf -v prct %.2f "$1"
((prct=10#${prct/.}*totlen/10000, prct%8)) &&
printf -v lastchar '\\U258%X' $(( 16 - prct%8 )) ||
lastchar=''
printf -v barstring '%*s' $((prct/8)) ''
printf -v barstring '%b' "${barstring// /\\U2588}$lastchar"
printf -v blankstring '%*s' $(((totlen-prct)/8)) ''
printf -v "$3" '%s%s' "$barstring" "$blankstring"
}
Usage:
# percentBar <float percent> <int string width> <variable name>
percentBar 42.42 $COLUMNS bar1
echo "$bar1"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
To show little differences:
percentBar 42.24 $COLUMNS bar2
printf "%s\n" "$bar1" "$bar2"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
As rendered variable is a fixed widht string, using color is easy:
percentBar 72.1 24 bar
printf 'Show this: \e[44;33;1m%s\e[0m at %s%%\n' "$bar" 72.1
for i in {0..10000..33} 10000;do i=0$i
printf -v p %0.2f ${i::-2}.${i: -2}
percentBar $p $((COLUMNS-9)) bar
printf '\r|%s|%6.2f%%' "$bar" $p
read -srt .002 _ && break # console sleep avoiding fork
done
|โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ|100.00%
clear; for i in {0..10000..33} 10000;do i=0$i
printf -v p %0.2f ${i::-2}.${i: -2}
percentBar $p $((COLUMNS-7)) bar
printf '\r\e[47;30m%s\e[0m%6.2f%%' "$bar" $p
read -srt .002 _ && break
done
Another demo showing different sizes and colored output:
printf '\n\n\n\n\n\n\n\n\e[8A\e7'&&for i in {0..9999..99} 10000;do
o=1 i=0$i;printf -v p %0.2f ${i::-2}.${i: -2}
for l in 1 2 3 5 8 13 20 40 $((COLUMNS-7));do
percentBar $p $l bar$((o++));done
[ "$p" = "100.00" ] && read -rst .8 _;printf \\e8
printf '%s\e[48;5;23;38;5;41m%s\e[0m%6.2f%%%b' 'In 1 char width: ' \
"$bar1" $p ,\\n 'with 2 chars: ' "$bar2" $p ,\\n 'or 3 chars: ' \
"$bar3" $p ,\\n 'in 5 characters: ' "$bar4" $p ,\\n 'in 8 chars: ' \
"$bar5" $p .\\n 'There are 13 chars: ' "$bar6" $p ,\\n '20 chars: '\
"$bar7" $p ,\\n 'then 40 chars' "$bar8" $p \
', or full width:\n' '' "$bar9" $p ''
((10#$i)) || read -st .5 _; read -st .1 _ && break
done
Could produce something like this:
sleep
with progress barRewrite feb 2023: Turn into more usefull displaySleep
function suitable to use as displayed timeout read:
This sleep show a progress bar with 50 refresh by seconds (tunnable)
percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
displaySleep() {
local -i refrBySeconds=50
local -i _start=${EPOCHREALTIME/.} reqslp target crtslp crtp cols cpos dlen
local strng percent prctbar tleft
[[ $COLUMNS ]] && cols=${COLUMNS} || read -r cols < <(tput cols)
refrBySeconds=' 1000000 / refrBySeconds '
printf -v strng %.6f $1
printf '\E[6n' && IFS=\; read -sdR _ cpos
dlen=${#strng}-1 cols=' cols - dlen - cpos -1 '
printf \\e7
reqslp=10#${strng/.} target=reqslp+_start
for ((;${EPOCHREALTIME/.}<target;)){
crtp=${EPOCHREALTIME/.}
crtslp='( target - crtp ) > refrBySeconds? refrBySeconds: target - crtp'
strng=00000$crtslp crtp+=-_start
printf -v strng %.6f ${strng::-6}.${strng: -6}
percent $crtp $reqslp percent
percentBar $percent $cols prctbar
tleft=00000$((reqslp-crtp))
printf '\e8\e[36;48;5;23m%s\e[0m%*.4fs' \
"$prctbar" "$dlen" ${tleft::-6}.${tleft: -6}
IFS= read -rsn1 -t $strng ${2:-_} && { echo; return;}
}
percentBar 100 $cols prctbar
printf '\e8\e[36;48;5;30m%s\e[0m%*.4fs\n' "$prctbar" "$dlen" 0
false
}
This will keep current cursor position to fill only the rest of line (full line if current cursor position is 1). This could be useful for displaying some kind of prompt:
sha1sum
with progress barUnder linux, you could find a lot of usefull infos under /proc
pseudo filesystem, so using previoulsy defined functions percentBar
and percent
, here is sha1progress
:
percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
sha1Progress() {
local -i totsize crtpos cols=$(tput cols) sha1in sha1pid
local sha1res percent prctbar
exec {sha1in}< <(exec sha1sum -b - <"$1")
sha1pid=$!
read -r totsize < <(stat -Lc %s "$1")
while ! read -ru $sha1in -t .025 sha1res _; do
read -r _ crtpos < /proc/$sha1pid/fdinfo/0
percent $crtpos $totsize percent
percentBar $percent $((cols-8)) prctbar
printf '\r\e[44;38;5;25m%s\e[0m%6.2f%%' "$prctbar" $percent;
done
printf "\r%s %s\e[K\n" $sha1res "$1"
}
Of course, 25 ms
timeout mean approx 40 refresh per second. This could look overkill, but work fine on my host, and anyway, this can be tunned.
Explanation:
exec {sha1in}<
create a new file descriptor for the output of<( ... )
forked task run in backgroundsha1sum -b - <"$1"
ensuring input came from STDIN (fd/0
)while ! read -ru $sha1in -t .025 sha1res _
While no input read from subtask, in 25 ms
.../proc/$sha1pid/fdinfo/0
kernel variable showing information about file descriptor 0 (STDIN) of task $sha1pid
If you don't have any to create a progress bar, but still want a spinner for ask user to wait, you could simply:
spinner() {
local shs=( - \\ \| / ) pnt=0
printf '\e7'
while ! read -rsn1 -t .2 _; do
printf '%b\e8' "${shs[pnt++%${#shs[@]}]}"
done
}
printf '\e[H\e[J Simple spinner: ';spinner
This could be stopped by hitting Any key.
So for using them as a backgroud task, you could use this two wrappers:
startSpinner () {
tput civis;
exec {doSpinner}> >(spinner "$@")
}
stopSpinner () {
echo >&"$doSpinner" && exec {doSpinner}>&-;
tput cnorm;
echo
}
printf '\e[H\e[J Simple spinner: ';startSpinner;sleep 4;stopSpinner
Then, for sample short stat of /usr
filesystem:
printf 'Scan %s for stats... ' "$HOME"; \
startSpinner; \
declare -A homeStats; \
while read cnt typ; do
homeStats["$typ"]=$cnt;
done < <(
find "$HOME" -printf entries\\n -type f -printf files\\n -o -type d \
-printf dirs\\n 2>/dev/null | sort | uniq -c); \
stopSpinner; \
printf 'There are %d entries in %s, %d directories and %d files.\n' \
${homeStats[entries]} "$HOME" ${homeStats[files]} ${homeStats[dirs]}
UTF-8 braille characters
for drawing a spinnerspinner(){
local pnt=0 shs=( 01 08 10 20 80 40 04 02 )
printf '\e7'
while ! read -sn 1 -t .2 _; do
printf %b\\e8 \\U28${shs[pnt++%${#shs[@]}]}
done
}
printf '\e[H\e[J Single dot braille: ';startSpinner;sleep 4;stopSpinner
More sophisticated shape, drawing a growing snake:
spinner(){
local pnt=0 shs=( 01 08 10 20 80 40 04 02 09 18 30 a0 c0 44 06 03 19 38
b0 e0 c4 46 07 0b 39 b8 f0 e4 c6 47 0f 1b b9 f8 f4 e6 c7 4f 1f 3b f9
fc f6 e7 cf 5f 3f bb fd fe f7 ef df 7f bf fb f9 fc f6 e7 cf 5f 3f bb
b9 f8 f4 e6 c7 4f 1f 3b 39 b8 f0 e4 c6 47 0f 1b 19 38 b0 e0 c4 46 07
0b 09 18 30 a0 c0 44 06 03 )
printf '\e7'
while ! read -sn 1 -t .2 _; do
printf %b\\e8 \\U28${shs[pnt++%${#shs[@]}]}
done
}
printf '\e[H\e[J Growing snake spinner: ';startSpinner;sleep 4;stopSpinner
This is an auto-build function:
spinner() {
local shs=( 100 800 1 8 10 20 80 40 8000 4000 400 200 100 900 801 9 18 30
A0 C0 8040 C000 4400 600 300 900 901 809 19 38 B0 E0 80C0 C040 C400 4600
700 B00 901 909 819 39 B8 F0 80E0 C0C0 C440 C600 4700 F00 B01 909 919
839 B9 F8 80F0 C0E0 C4C0 C640 C700 4F00 F01 B09 919 939 8B9 F9 80F8
C0F0 C4E0 C6C0 C740 CF00 4F01 F09 B19 939 9B9 8F9 80F9 C0F8 C4F0 C6E0
C7C0 CF40 CF01 4F09 F19 B39 9B9 9F9 88F9 C0F9 C4F8 C6F0 C7E0 CFC0 CF41
CF09 4F19 F39 BB9 9F9 89F9 C8F9 C4F9 C6F8 C7F0 CFE0 CFC1 CF49 CF19 4F39
FB9 BF9 89F9 C9F9 CCF9 C6F9 C7F8 CFF0 CFE1 CFC9 CF59 CF39 4FB9 FF9 8BF9
C9F9 CDF9 CEF9 C7F9 CFF8 CFF1 CFE9 CFD9 CF79 CFB9 4FF9 8FF9 C9F9 CCF9
C6F9 C7F8 CFF0 CFE1 CFC9 CF59 CF39 4FB9 FF9 89F9 C8F9 C4F9 C6F8 C7F0
CFE0 CFC1 CF49 CF19 4F39 FB9 9F9 88F9 C0F9 C4F8 C6F0 C7E0 CFC0 CF41
CF09 4F19 F39 9B9 8F9 80F9 C0F8 C4F0 C6E0 C7C0 CF40 CF01 4F09 F19 939
8B9 F9 80F8 C0F0 C4E0 C6C0 C740 CF00 4F01 F09 919 839 B9 F8 80F0 C0E0
C4C0 C640 C700 4F00 F01 909 819 39 B8 F0 80E0 C0C0 C440 C600 4700 F00
901 809 19 38 B0 E0 80C0 C040 C400 4600 700 900 801 9 18 30 A0 C0 8040
C000 4400 600 ) chr
local -i pnt
for pnt in "${!shs[@]}"; do
chr="000${shs[pnt]}"
printf -v shs[pnt] '%b' "\U28${chr: -4:2}\U28${chr: -2}"
done
eval "${FUNCNAME[0]}() { local shs=( ${shs[*]@Q}) "'
local -i pnt;printf '\''\e7'\'';
while ! read -rsn1 -t "${1:-.02}"; do
printf '\''%b\e8'\'' "${shs[pnt++%${#shs[@]}]}";
done;}';
};spinner
Then:
printf 'Snake two braille chars: ';startSpinner ; sleep 12 ; stopSpinner
If you find this fun, you may found a 8 characters: four columns on two lines growing snake spinner at My eight braille pattern progressive snake spinner ( @ Code Golf ;-)
โกโ โ โขน
โฃโฃโฃโฃธ
I've posted a full grep
progress bar script answering grep - how to output progress bar or status
using a spinner while building list of files to grep using shopt -s globstar
and files=(**)
drawing two progress bar:
Promise for a parallel grep script (currently under debugging phase...)
Upvotes: 48
Reputation: 19375
Some posts have showed how to display the command's progress. In order to calculate it, you'll need to see how much you've progressed. On BSD systems some commands, such as dd(1), accept a SIGINFO
signal, and will report their progress. On Linux systems some commands will respond similarly to SIGUSR1
. If this facility is available, you can pipe your input through _dd to monitor the number of bytes processed.
Alternatively, you can use lsof to obtain the offset of the file's read pointer, and thereby calculate the progress.
Here is an example of using lsof(1) to see the progress of wc(1) reading a large file named blob
.
$ wc -l blob &
[1] 3405769
$ lsof -w -o0 -o -c wc
COMMAND PID USER FD TYPE DEVICE OFFSET NODE NAME
[...]
wc 3405769 dds 3r REG 254,7 0t2656059392 7733716 blob
I've written a command, named pmonitor, that displays the progress of processing a specified process or file. With it you can do things, such as the following.
$ pmonitor -c gzip
/home/dds/data/mysql-2015-04-01.sql.gz 58.06%
An earlier version of Linux and FreeBSD shell scripts appears on my blog ("Monitor Process Progress on Unix").
Upvotes: 54
Reputation: 439
SIMPLE COUNTER FOR NOT SO FANCY JOBS
I don't need really a progress bar, just a visual feedback of progress, so I prefer a simple counter after enabling the command monitored to output lines processed:
#!/bin/bash
# pvlines: simple script to count stdin lines read
# copy/chmod a+rx to /usr/local/bin and use: command | pvlines
awk '{N++; printf "\r%d", N} END {print ""}'
Create the script as indicated in the comments, then use like this:
tar zcvf backup.tgz dir-to-backup | pvlines
To process a large amount of files, first count them and then do the process, so we'll have a rough estimate of termination:
find /dir-to-copy | pvlines
rsync -avr /dir-to-copy user@bkserver:destination/ | pvlines
I found this counter have a reduced impact on the process, but it can be modified to skip some lines and report, say, each 200 or 2000 lines read.
First I did that in pure Bash with a while/read/printf loop, but it was too slow, so I migrated pvlines
to awk
.
Upvotes: 0
Reputation: 1710
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
- print to terminal (i.e. to stdout
)echo -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
foo=$(bar)
- run bar
in a subprocess and save its stdout
to $foo
stat
- print file stats to stdout
stat -c
- print a formatted value%s
- format for 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
- print info about zip archivetail -n1
- work with 1 line from the bottomtr -s ' '
- translate multiple spaces into one ("squeeze" them)cut -d' ' -f3
- cut 3rd space-delimited field (column)Here's the meat of the problem I mentioned before. 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/bash
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 but pad it with 50 spacestr ' ' '#'
- translate every space to hash signAnd this is how you'd use it:
#!/bin/bash
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 you can wrap this in a function, rewrite to work with piped streams, grab forked process ID with $!
and pass it to progress_bar.sh
so it could guess how to calculate work to do and work done, whatever's your poison.
I get asked about these two things most often:
${}
: in above examples I use ${foo:A:B}
. The technical term for this syntax is Parameter Expansion, a built-in shell functionality that allows to manipulate a variable (parameter), e.g. to trim a string with :
but also to do other things - it does not spawn a subshell. The most prominent description of parameter expansion I can think of (that isn't fully POSIX compatible but lets the reader understand the concept well) is in the man bash
page.$()
: in above examples I use foo=$(bar)
. It spawns a separate shell in a subprocess (a.k.a. a Subshell), runs the bar
command in it and assigns its standard output to a $foo
variable. It's not the same as Process Substitution and it's something entirely different than pipe (|
). Most importantly, it works. Some say this should be avoided because it's slow. I argue this is "a okay" here because whatever this code is trying to visualise lasts long enough to require a progress bar. In other words, subshells are not the bottleneck. Calling a subshell also saves me the effort of explaining why return
isn't what most people think it is, what is an Exit Status and why passing values from functions in shells is not what shell functions are good at in general. To find out more about all of it I, again, highly recommend the man bash
page.If your shell is actually running sh instead of bash, or really old bash, like default osx, it may choke on echo -ne "\r${BAR:0:$i}"
. The exact error is Bad substitution
. If this happens to you, per the comment section, you can instead use echo -ne "\r$(expr "x$name" : "x.\{0,$num_skip\}\(.\{0,$num_keep\}\)")"
to do a more portable posix-compatible / less readable substring match.
A complete, working /bin/sh example:
#!/bin/sh
src=100
tgt=0
get_work_todo() {
echo $src
}
do_work() {
echo "$(( $1 + 1 ))"
}
BAR_length=50
BAR_character='#'
BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
work_todo=$(get_work_todo) # how much there is to do
work_done=0
while true; do
work_done="$(do_work $work_done)"
i=$(( $work_done * $BAR_length / $work_todo ))
n=$(( $BAR_length - $i ))
printf "\r$(expr "x$BAR" : "x.\{0,$n\}\(.\{0,$i\}\)")"
if [ $work_done = $work_todo ]; then
echo "\n"
break;
fi
sleep .1
done
Upvotes: 37
Reputation: 743
Use the Linux command pv
.
It doesn't know the size if it's in the middle of the pipeline, but it gives a speed and total, and from there you can figure out how long it should take and get feedback so you know it hasn't hung.
Upvotes: 71
Reputation: 1949
I needed a progress bar that would fit in popup bubble message (notify-send
) to represent TV volume level. Recently I've been writing a music player in python and the TV picture is turned off most of the time.
#!/bin/bash
# Show a progress bar at step number $1 (from 0 to 100)
function is_int() { test "$@" -eq "$@" 2> /dev/null; }
# Parameter 1 must be integer
if ! is_int "$1" ; then
echo "Not an integer: ${1}"
exit 1
fi
# Parameter 1 must be >= 0 and <= 100
if [ "$1" -ge 0 ] && [ "$1" -le 100 ] 2>/dev/null
then
:
else
echo bad volume: ${1}
exit 1
fi
# Main function designed for quickly copying to another program
Main () {
Bar="" # Progress Bar / Volume level
Len=25 # Length of Progress Bar / Volume level
Div=4 # Divisor into Volume for # of blocks
Fill="โ" # Fill up to $Len
Arr=( "โ" "โ" "โ" "โ" ) # UTF-8 left blocks: 7/8, 1/4, 1/2, 3/4
FullBlock=$((${1} / Div)) # Number of full blocks
PartBlock=$((${1} % Div)) # Size of partial block (array index)
while [[ $FullBlock -gt 0 ]]; do
Bar="$Bar${Arr[0]}" # Add 1 full block into Progress Bar
(( FullBlock-- )) # Decrement full blocks counter
done
# If remainder zero no partial block, else append character from array
if [[ $PartBlock -gt 0 ]]; then
Bar="$Bar${Arr[$PartBlock]}"
fi
while [[ "${#Bar}" -lt "$Len" ]]; do
Bar="$Bar$Fill" # Pad Progress Bar with fill character
done
echo Volume: "$1 $Bar"
exit 0 # Remove this line when copying into program
} # Main
Main "$@"
Use this script to test the progress bar in the terminal.
#!/bin/bash
# test_progress_bar3
Main () {
tput civis # Turn off cursor
for ((i=0; i<=100; i++)); do
CurrLevel=$(./progress_bar3 "$i") # Generate progress bar 0 to 100
echo -ne "$CurrLevel"\\r # Reprint overtop same line
sleep .04
done
echo -e \\n # Advance line to keep last progress
echo "$0 Done"
tput cnorm # Turn cursor back on
} # Main
Main "$@"
This section details how notify-send
is used to quickly spam popup bubble messages to the desktop. This is required because volume level can change many times a second and the default bubble message behavior is for a message to stay on the desktop for many seconds.
From the script above the main
function was copied to a new functioned called VolumeBar
in an existing bash script called tvpowered
. The exit 0
command in the copied main
function was removed.
Here's how to call it and let Ubuntu's notify-send
command know we will be spamming popup bubble message:
VolumeBar $CurrVolume
# Ask Ubuntu: https://askubuntu.com/a/871207/307523
notify-send --urgency=critical "tvpowered" \
-h string:x-canonical-private-synchronous:volume \
--icon=/usr/share/icons/gnome/48x48/devices/audio-speakers.png \
"Volume: $CurrVolume $Bar"
This is the new line which tells notify-send
to immediately replace last popup bubble:
-h string:x-canonical-private-synchronous:volume \
volume
groups the popup bubble messages together and new messages in this group immediately replaces the previous. You can use anything
instead of volume
.
Upvotes: 5
Reputation: 3236
I needed a progress bar for iterating over the lines in a csv file. Was able to adapt cprn's code into something useful for me:
BAR='##############################'
FILL='------------------------------'
totalLines=$(wc -l $file | awk '{print $1}') # num. lines in file
barLen=30
# --- iterate over lines in csv file ---
count=0
while IFS=, read -r _ col1 col2 col3; do
# update progress bar
count=$(($count + 1))
percent=$((($count * 100 / $totalLines * 100) / 100))
i=$(($percent * $barLen / 100))
echo -ne "\r[${BAR:0:$i}${FILL:$i:barLen}] $count/$totalLines ($percent%)"
# other stuff
(...)
done <$file
Looks like this:
[##----------------------------] 17128/218210 (7%)
Upvotes: 6
Reputation: 636
There are many different answers about this topic, but when calculating percentage for text file operation, using current length / total size
way, for example showing percentage of ver_big_file.json
progress, and I recommend using awk
for this purpose, like below code:
awk '
function bar(x){s="";i=0;while (i++ < x) s=s "#";return s}
BEGIN{
("ls -l " ARGV[1]) | getline total;
split(total,array);
total=array[5];
}
{
cur+=length($0)+1;
percent=int(cur / total * 100);
printf "LINE %s:%s %s%%\r", NR, bar(percent*.8), percent
}
END {print}' very_big_file.json | grep "keyword" | ...
This way it's very precise, stream based, but it's only suitable for text files.
Upvotes: 0
Reputation: 881
It may be achieved in a pretty simple way:
for
loop$bar
variable another =
sign to make the progress bar wider\r
cleans line and returns to the beginning of the line; -ne
makes echo
doesn't add newline at the end and parses \r
special character)function progress {
bar=''
for (( x=0; x <= 100; x++ )); do
sleep 0.25
bar="${bar}="
echo -ne "$bar ${x}%\r"
done
echo -e "\n"
}
$ progress
> ========== 10% # here: after 2.5 seconds
$ progress
> ============================== 30% # here: after 7.5 seconds
COLORED PROGRESS BAR
function progress {
bar=''
for (( x=0; x <= 100; x++ )); do
sleep 0.05
bar="${bar} "
echo -ne "\r"
echo -ne "\e[43m$bar\e[0m"
local left="$(( 100 - $x ))"
printf " %${left}s"
echo -n "${x}%"
done
echo -e "\n"
}
To make a progress bar colorful, you can use formatting escape sequence - here the progress bar is yellow: \e[43m
, then we reset custom settings with \e[0m
, otherwise it would affect further input even when the progress bar is done.
Upvotes: 2
Reputation: 7062
Flexible version with randomized colors, a string to manipulate and date.
function spinner() {
local PID="$1"
local str="${2:-Processing!}"
local delay="0.1"
# tput civis # hide cursor
while ( kill -0 $PID 2>/dev/null )
do
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ๐ ๐ ๐ $str ๐ ๐ ๐ ]"; sleep "$delay"
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ๐ ๐ ๐ $str ๐ ๐ ๐ ]"; sleep "$delay"
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ๐ ๐ ๐ $str ๐ ๐ ๐ ]"; sleep "$delay"
done
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ โ
โ
โ
Done! โ
โ
โ
]"; sleep "$delay"
# tput cnorm # restore cursor
return 0
}
Usage:
# your long running proccess pushed to the background
sleep 20 &
# spinner capture-previous-proccess-id string
spinner $! 'Working!'
output example:
[04/06/2020 03:22:24][ ๐ ๐ ๐ Seeding! ๐ ๐ ๐ ]
Upvotes: 1
Reputation: 837
Got an easy progress bar function that i wrote the other day:
#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState($1) and totalState($2)
function ProgressBar {
# Process data
let _progress=(${1}*100/${2}*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
# Build progressbar string lengths
_fill=$(printf "%${_done}s")
_empty=$(printf "%${_left}s")
# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"
}
# Variables
_start=1
# This accounts as the "totalState" variable for the ProgressBar function
_end=100
# Proof of concept
for number in $(seq ${_start} ${_end})
do
sleep 0.1
ProgressBar ${number} ${_end}
done
printf '\nFinished!\n'
Or snag it from,
https://github.com/fearside/ProgressBar/
Upvotes: 82
Reputation: 417
EDIT: For an updated version check my github page
I was not satisfied with the responses on this question. What I was personally looking for was a fancy progress bar as is seen by APT.
I had a look at the C source code for APT and decided to write my own equivalent for bash.
This progress bar will stay nicely at the bottom of the terminal and will not interfere with any output sent to the terminal.
Please do note that the bar is currently fixed at 100 characters wide. If you want scale it to the size of the terminal, this is fairly easy to accomplish as well (The updated version on my github page handles this well).
I will post my script here. Usage example:
source ./progress_bar.sh
echo "This is some output"
setup_scroll_area
sleep 1
echo "This is some output 2"
draw_progress_bar 10
sleep 1
echo "This is some output 3"
draw_progress_bar 50
sleep 1
echo "This is some output 4"
draw_progress_bar 90
sleep 1
echo "This is some output 5"
destroy_scroll_area
The script (I strongly recommend the version on my github instead):
#!/bin/bash
# This code was inspired by the open source C code of the APT progress bar
# http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/apt/trusty/view/head:/apt-pkg/install-progress.cc#L233
#
# Usage:
# Source this script
# setup_scroll_area
# draw_progress_bar 10
# draw_progress_bar 90
# destroy_scroll_area
#
CODE_SAVE_CURSOR="\033[s"
CODE_RESTORE_CURSOR="\033[u"
CODE_CURSOR_IN_SCROLL_AREA="\033[1A"
COLOR_FG="\e[30m"
COLOR_BG="\e[42m"
RESTORE_FG="\e[39m"
RESTORE_BG="\e[49m"
function setup_scroll_area() {
lines=$(tput lines)
let lines=$lines-1
# Scroll down a bit to avoid visual glitch when the screen area shrinks by one row
echo -en "\n"
# Save cursor
echo -en "$CODE_SAVE_CURSOR"
# Set scroll region (this will place the cursor in the top left)
echo -en "\033[0;${lines}r"
# Restore cursor but ensure its inside the scrolling area
echo -en "$CODE_RESTORE_CURSOR"
echo -en "$CODE_CURSOR_IN_SCROLL_AREA"
# Start empty progress bar
draw_progress_bar 0
}
function destroy_scroll_area() {
lines=$(tput lines)
# Save cursor
echo -en "$CODE_SAVE_CURSOR"
# Set scroll region (this will place the cursor in the top left)
echo -en "\033[0;${lines}r"
# Restore cursor but ensure its inside the scrolling area
echo -en "$CODE_RESTORE_CURSOR"
echo -en "$CODE_CURSOR_IN_SCROLL_AREA"
# We are done so clear the scroll bar
clear_progress_bar
# Scroll down a bit to avoid visual glitch when the screen area grows by one row
echo -en "\n\n"
}
function draw_progress_bar() {
percentage=$1
lines=$(tput lines)
let lines=$lines
# Save cursor
echo -en "$CODE_SAVE_CURSOR"
# Move cursor position to last row
echo -en "\033[${lines};0f"
# Clear progress bar
tput el
# Draw progress bar
print_bar_text $percentage
# Restore cursor position
echo -en "$CODE_RESTORE_CURSOR"
}
function clear_progress_bar() {
lines=$(tput lines)
let lines=$lines
# Save cursor
echo -en "$CODE_SAVE_CURSOR"
# Move cursor position to last row
echo -en "\033[${lines};0f"
# clear progress bar
tput el
# Restore cursor position
echo -en "$CODE_RESTORE_CURSOR"
}
function print_bar_text() {
local percentage=$1
# Prepare progress bar
let remainder=100-$percentage
progress_bar=$(echo -ne "["; echo -en "${COLOR_FG}${COLOR_BG}"; printf_new "#" $percentage; echo -en "${RESTORE_FG}${RESTORE_BG}"; printf_new "." $remainder; echo -ne "]");
# Print progress bar
if [ $1 -gt 99 ]
then
echo -ne "${progress_bar}"
else
echo -ne "${progress_bar}"
fi
}
printf_new() {
str=$1
num=$2
v=$(printf "%-${num}s" "$str")
echo -ne "${v// /$str}"
}
Upvotes: 30
Reputation: 9857
#!/bin/bash
tot=$(wc -c /proc/$$/fd/255 | awk '/ /{print $1}')
now() {
echo $(( 100* ($(awk '/^pos:/{print $2}' < /proc/$$/fdinfo/255)-166) / (tot-166) )) "%"
}
now;
now;
now;
now;
now;
now;
now;
now;
now;
output:
0 %
12 %
25 %
37 %
50 %
62 %
75 %
87 %
100 %
note: if instead of 255 you put 1 you will monitor the standard in...with 2 the standard out (but you must modify the source to set "tot" to the projected output file size)
Upvotes: 0
Reputation: 79
Using suggestions listed above, I decided to implement my own progress bar.
#!/usr/bin/env bash
main() {
for (( i = 0; i <= 100; i=$i + 1)); do
progress_bar "$i"
sleep 0.1;
done
progress_bar "done"
exit 0
}
progress_bar() {
if [ "$1" == "done" ]; then
spinner="X"
percent_done="100"
progress_message="Done!"
new_line="\n"
else
spinner='/-\|'
percent_done="${1:-0}"
progress_message="$percent_done %"
fi
percent_none="$(( 100 - $percent_done ))"
[ "$percent_done" -gt 0 ] && local done_bar="$(printf '#%.0s' $(seq -s ' ' 1 $percent_done))"
[ "$percent_none" -gt 0 ] && local none_bar="$(printf '~%.0s' $(seq -s ' ' 1 $percent_none))"
# print the progress bar to the screen
printf "\r Progress: [%s%s] %s %s${new_line}" \
"$done_bar" \
"$none_bar" \
"${spinner:x++%${#spinner}:1}" \
"$progress_message"
}
main "$@"
Upvotes: 1
Reputation: 1013
Uploading a file
[##################################################] 100% (137921 / 137921 bytes)
Waiting for a job to complete
[######################### ] 50% (15 / 30 seconds)
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"
}
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"
Upvotes: 13
Reputation: 840
https://github.com/extensionsapp/progre.sh
Create 40 percent progress: progreSh 40
Upvotes: 2
Reputation: 11834
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:
\n
and\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).
Upvotes: 800
Reputation: 43421
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
Upvotes: 55
Reputation: 7337
#!/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
Upvotes: 0
Reputation: 1699
Based on the work of Edouard Lopez, I created a progress bar that fits the size of the screen, whatever it is. Check it out.
It's also posted on Git Hub.
#!/bin/bash
#
# Progress bar by Adriano Pinaffo
# Available at https://github.com/adriano-pinaffo/progressbar.sh
# Inspired on work by Edouard Lopez (https://github.com/edouard-lopez/progress-bar.sh)
# Version 1.0
# Date April, 28th 2017
function error {
echo "Usage: $0 [SECONDS]"
case $1 in
1) echo "Pass one argument only"
exit 1
;;
2) echo "Parameter must be a number"
exit 2
;;
*) echo "Unknown error"
exit 999
esac
}
[[ $# -ne 1 ]] && error 1
[[ $1 =~ ^[0-9]+$ ]] || error 2
duration=${1}
barsize=$((`tput cols` - 7))
unity=$(($barsize / $duration))
increment=$(($barsize%$duration))
skip=$(($duration/($duration-$increment)))
curr_bar=0
prev_bar=
for (( elapsed=1; elapsed<=$duration; elapsed++ ))
do
# Elapsed
prev_bar=$curr_bar
let curr_bar+=$unity
[[ $increment -eq 0 ]] || {
[[ $skip -eq 1 ]] &&
{ [[ $(($elapsed%($duration/$increment))) -eq 0 ]] && let curr_bar++; } ||
{ [[ $(($elapsed%$skip)) -ne 0 ]] && let curr_bar++; }
}
[[ $elapsed -eq 1 && $increment -eq 1 && $skip -ne 1 ]] && let curr_bar++
[[ $(($barsize-$curr_bar)) -eq 1 ]] && let curr_bar++
[[ $curr_bar -lt $barsize ]] || curr_bar=$barsize
for (( filled=0; filled<=$curr_bar; filled++ )); do
printf "โ"
done
# Remaining
for (( remain=$curr_bar; remain<$barsize; remain++ )); do
printf " "
done
# Percentage
printf "| %s%%" $(( ($elapsed*100)/$duration))
# Return
sleep 1
printf "\r"
done
printf "\n"
exit 0
Enjoy
Upvotes: 5
Reputation: 37461
You may also be interested in how to do a spinner:
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
Upvotes: 125
Reputation: 1441
I wanted to track progress based on the number of lines a command output against a target number of lines from a previous run:
#!/bin/bash
function lines {
local file=$1
local default=$2
if [[ -f $file ]]; then
wc -l $file | awk '{print $1}';
else
echo $default
fi
}
function bar {
local items=$1
local total=$2
local size=$3
percent=$(($items*$size/$total % $size))
left=$(($size-$percent))
chars=$(local s=$(printf "%${percent}s"); echo "${s// /=}")
echo -ne "[$chars>";
printf "%${left}s"
echo -ne ']\r'
}
function clearbar {
local size=$1
printf " %${size}s "
echo -ne "\r"
}
function progress {
local pid=$1
local total=$2
local file=$3
bar 0 100 50
while [[ "$(ps a | awk '{print $1}' | grep $pid)" ]]; do
bar $(lines $file 0) $total 50
sleep 1
done
clearbar 50
wait $pid
return $?
}
Then use it like this:
target=$(lines build.log 1000)
(mvn clean install > build.log 2>&1) &
progress $! $target build.log
It outputs a progress bar that looks something like this:
[===============================================> ]
The bar grows as the number of lines output reaches the target. If the number of lines exceeds the target, the bar starts over (hopefully the target is good).
BTW: I'm using bash on Mac OSX. I based this code on a spinner from mariascio.
Upvotes: -1
Reputation: 384
First execute the process to the background, then watch it's running status frequently,that was running print the pattern and again check it status was running or not;
Using while loop to watch the status of the process frequently.
use the pgrep or any other command to watch and getting running status of a process.
if using pgrep redirect the unnecessary output to /dev/null as needed.
Code:
sleep 12&
while pgrep sleep &> /dev/null;do echo -en "#";sleep 0.5;done
This "#" will printed until sleep terminate,this method used to implement the progress bar for progress time of program.
you can also use this method to the commands to shell scripts for analyze it process time as visual.
BUG: this pgrep method doesn't works in all situations,unexpectedly the another process was running with same name, the while loop does not end.
so getting the process running status by specify it's PID, using may the process can available with some commands,
the command ps a will list all the process with id,you need grep to find-out the pid of the specified process
Upvotes: -1
Reputation: 1
This is a psychedelic progressbar for bash scripting by nExace. It can be called from command line as './progressbar x y' where 'x' is a time in seconds and 'y' is a message associated with that portion of the progress.
The inner progressbar() function itself is good standalone as well if you want other portions of your script to control the progressbar. For instance, sending 'progressbar 10 "Creating directory tree";' will display:
[####### ] (10%) Creating directory tree
Of course it will be nicely psychedelic though...
#!/bin/bash
if [ "$#" -eq 0 ]; then echo "x is \"time in seconds\" and z is \"message\""; echo "Usage: progressbar x z"; exit; fi
progressbar() {
local loca=$1; local loca2=$2;
declare -a bgcolors; declare -a fgcolors;
for i in {40..46} {100..106}; do
bgcolors+=("$i")
done
for i in {30..36} {90..96}; do
fgcolors+=("$i")
done
local u=$(( 50 - loca ));
local y; local t;
local z; z=$(printf '%*s' "$u");
local w=$(( loca * 2 ));
local bouncer=".oOยฐOo.";
for ((i=0;i<loca;i++)); do
t="${bouncer:((i%${#bouncer})):1}"
bgcolor="\\E[${bgcolors[RANDOM % 14]}m \\033[m"
y+="$bgcolor";
done
fgcolor="\\E[${fgcolors[RANDOM % 14]}m"
echo -ne " $fgcolor$t$y$z$fgcolor$t \\E[96m(\\E[36m$w%\\E[96m)\\E[92m $fgcolor$loca2\\033[m\r"
};
timeprogress() {
local loca="$1"; local loca2="$2";
loca=$(bc -l <<< scale=2\;"$loca/50")
for i in {1..50}; do
progressbar "$i" "$loca2";
sleep "$loca";
done
printf "\n"
};
timeprogress "$1" "$2"
Upvotes: -1
Reputation: 14829
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
Upvotes: 4
Reputation: 1591
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.
Upvotes: 2
Reputation: 1
I have built on the answer provided by fearside
This connects to an Oracle database to retrieve the progress of an RMAN restore.
#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState($1) and totalState($2)
function ProgressBar {
# Process data
let _progress=(${1}*100/${2}*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
# Build progressbar string lengths
_fill=$(printf "%${_done}s")
_empty=$(printf "%${_left}s")
# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"
}
function rman_check {
sqlplus -s / as sysdba <<EOF
set heading off
set feedback off
select
round((sofar/totalwork) * 100,0) pct_done
from
v\$session_longops
where
totalwork > sofar
AND
opname NOT LIKE '%aggregate%'
AND
opname like 'RMAN%';
exit
EOF
}
# Variables
_start=1
# This accounts as the "totalState" variable for the ProgressBar function
_end=100
_rman_progress=$(rman_check)
#echo ${_rman_progress}
# Proof of concept
#for number in $(seq ${_start} ${_end})
while [ ${_rman_progress} -lt 100 ]
do
for number in _rman_progress
do
sleep 10
ProgressBar ${number} ${_end}
done
_rman_progress=$(rman_check)
done
printf '\nFinished!\n'
Upvotes: 0
Reputation: 59
I prefer to use dialog with the --gauge param. Is used very often in .deb package installations and other basic configuration stuff of many distros. So you don't need to reinvent the wheel... again
Just put an int value from 1 to 100 @stdin. One basic and silly example:
for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
I have this /bin/Wait file (with chmod u+x perms) for cooking purposes :P
#!/bin/bash
INIT=`/bin/date +%s`
NOW=$INIT
FUTURE=`/bin/date -d "$1" +%s`
[ $FUTURE -a $FUTURE -eq $FUTURE ] || exit
DIFF=`echo "$FUTURE - $INIT"|bc -l`
while [ $INIT -le $FUTURE -a $NOW -lt $FUTURE ]; do
NOW=`/bin/date +%s`
STEP=`echo "$NOW - $INIT"|bc -l`
SLEFT=`echo "$FUTURE - $NOW"|bc -l`
MLEFT=`echo "scale=2;$SLEFT/60"|bc -l`
TEXT="$SLEFT seconds left ($MLEFT minutes)";
TITLE="Waiting $1: $2"
sleep 1s
PTG=`echo "scale=0;$STEP * 100 / $DIFF"|bc -l`
echo $PTG| dialog --title "$TITLE" --gauge "$TEXT" 7 72
done
if [ "$2" == "" ]; then msg="Espera terminada: $1";audio="Listo";
else msg=$2;audio=$2;fi
/usr/bin/notify-send --icon=stock_appointment-reminder-excl "$msg"
espeak -v spanish "$audio"
So I can put:
Wait "34 min" "warm up the oven"
or
Wait "dec 31" "happy new year"
Upvotes: 3
Reputation: 11
I used an answer from Creating string of repeated characters in shell script for char repeating. I have two relatively small bash versions for scripts that need to display progress bar (for example, a loop that goes through many files, but not useful for big tar files or copy operations). The faster one consists of two functions, one to prepare the strings for bar display:
preparebar() {
# $1 - bar length
# $2 - bar char
barlen=$1
barspaces=$(printf "%*s" "$1")
barchars=$(printf "%*s" "$1" | tr ' ' "$2")
}
and one to display a progress bar:
progressbar() {
# $1 - number (-1 for clearing the bar)
# $2 - max number
if [ $1 -eq -1 ]; then
printf "\r $barspaces\r"
else
barch=$(($1*barlen/$2))
barsp=$((barlen-barch))
printf "\r[%.${barch}s%.${barsp}s]\r" "$barchars" "$barspaces"
fi
}
It could be used as:
preparebar 50 "#"
which means prepare strings for bar with 50 "#" characters, and after that:
progressbar 35 80
will display the number of "#" characters that corresponds to 35/80 ratio:
[##################### ]
Be aware that function displays the bar on the same line over and over until you (or some other program) prints a newline. If you put -1 as first parameter, the bar would be erased:
progressbar -1 80
The slower version is all in one function:
progressbar() {
# $1 - number
# $2 - max number
# $3 - number of '#' characters
if [ $1 -eq -1 ]; then
printf "\r %*s\r" "$3"
else
i=$(($1*$3/$2))
j=$(($3-i))
printf "\r[%*s" "$i" | tr ' ' '#'
printf "%*s]\r" "$j"
fi
}
and it can be used as (the same example as above):
progressbar 35 80 50
If you need progressbar on stderr, just add >&2
at the end of each printf command.
Upvotes: 1
Reputation: 137
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.
Upvotes: 12