0

I am trying to print a progress bar in shell as follows

#!/bin/bash

sleep $1 & PID=$!
printf "["
counter=1
while kill -0 $PID 2> /dev/null; do 
    percent_done=$(echo "${counter}*100/${1}" | bc -l )
    printf  "▓%.0f" $percent_done
    sleep 1
    counter=$counter+1
done
    printf "] done!"

and i call it as

myScript.sh 10

so the script will invoke a sleep of 1 sec for 10 seconds. On each iteration of the loop, i want to print

▓<the percentage of the loop completed>

for instance i expect

▓10▓20▓30▓40▓50▓60 and so on. but i get

▓10▓11▓12▓13▓14▓15 instead

what am i doing wrong? am i not updating the counter properly? also, if i do

percent_done=$(echo "${counter}/${1}" | bc -l )

then i get

▓0.1000000000000▓1.100000000000000000▓2.10000000000000000000▓3.10000000000000000000▓ and so on.

Since I dont want the long floating point value, i added the *100 in the expression.

What am i doing wrong?

AbtPst
  • 7,778
  • 17
  • 91
  • 172
  • 1
    Why are you using floating-point math here? Work with 100 rather than 1.0 as your point of completion, and there's no need for bc; even if you want five decimal points of precision, you could work with a number out of 10000, divide by (the integer) 100 to get the value to the left of the decimal point, take the remainder of dividing by 100 to get the value to the right, and again, you have a value with purely integer math (and thus no need for `bc` or `%f` format strings) at all. – Charles Duffy Apr 20 '18 at 20:31
  • hmm, makes sense, but i really dont know how to translate that into the shell script. could you please help me out with the syntax a bit? i am not that familiar with shell scripting – AbtPst Apr 20 '18 at 20:38

2 Answers2

2
$ function tt {
sleep $1 & PID=$!
printf "["
counter=10
while kill -0 $PID 2> /dev/null; do
percent_done=$(echo "${counter} |bc -l)
printf  "▓%.0f" $percent_done
sleep 1
counter=$counter+10
done
printf "] done!"
}
$ tt 10
[▓10▓20▓30▓40▓50▓60▓70▓80▓90▓100

You can further simplify your code by declaring an integer to bash:

$ function t {
  sleep $1 & PID=$!; 
  printf "["; 
  declare -i counter=10; 
  while kill -0 $PID 2> /dev/null; do printf  "▓%.0f" $counter; 
  sleep 1; 
  counter=$counter+10; done; 
  printf "] done!"; }
$ t 20
[1] 4227
[▓10▓20▓30▓40▓50▓60▓70▓80▓90▓100▓110▓120▓130▓140▓150▓160▓170▓180▓190▓200[1]+  Done 
George Vasiliou
  • 6,130
  • 2
  • 20
  • 27
  • thanks, but for an input of `20`, the above prints `[▓5▓15▓25▓35▓45▓55▓65▓75▓85▓95▓105▓115▓125▓135▓145▓155▓165▓175▓185▓195] done!` – AbtPst Apr 20 '18 at 19:57
  • @AbtPst Updated further – George Vasiliou Apr 20 '18 at 20:15
  • thanks for your help. i would really like to print the percentage of the loop completed thus far. so even for an input of 20, i should only see numbers between 0 and 100 – AbtPst Apr 20 '18 at 20:34
1

I guess counter=$counter+1 does not do what you expect. It just concatenates $counter and "+1". So after n iterations you'll have some sting like "1+1+...+1" (n+1-times). You have that in the expression you pipe to bc and operator precedence (* before +) then leads to the unexpected results.

Change counter=$counter+1 to also use bc as you've done above, thus counter=$(echo "${counter}+1" | bc).

Or change percent_done=$(echo "${counter}*100/${1}" | bc -l ) to percent_done=$(echo "(${counter})*100/${1}" | bc -l ) (note the parenthesis) to overrule operator precedence, if for some reason you really want such a "1+1..+1" string.

sticky bit
  • 36,626
  • 12
  • 31
  • 42
  • 1
    `counter=$(( counter + 1 ))` is *far* more efficient. `bc` is needed for floating-point math, but for integer math it's utterly unnecessary and (like any other external command) takes several orders of magnitude longer to invoke than shell-builtin functionality. – Charles Duffy Apr 20 '18 at 20:24