30

For a bash timer i use this code:

#!/bin/bash
sek=60
echo "60 Seconds Wait!"
echo -n "One Moment please "
while [ $sek -ge 1 ]
do
   echo -n "$sek "  
sleep 1
   sek=$[$sek-1]
done
echo
echo "ready!"

That gives me something like that

One Moment please: 60 59 58 57 56 55 ...

Is there a possibility to replace the last value of second by the most recent so that the output doesn't generate a large trail but the seconds countdown like a real time at one position? (Hope you understand what i mean :))

NES
  • 33,195

7 Answers7

23

Basically the same as aneeshep's answer, but uses Return (\r) rather than Backspace (\b) because we don't know if the length will always be the same, e.g. when $sek < 10.

Also, your first echo should use $sek, not hard-code 60.

Finally, note the space after the ....

#!/bin/bash
sek=60
echo "$sek Seconds Wait!"
while [ $sek -ge 1 ]
do
   echo -ne "One Moment please $sek ... \r"
   sleep 1
   sek=$[$sek-1]
done
echo
echo "ready!"
Mikel
  • 6,558
15
#!/bin/bash
sek=60
echo "60 Seconds Wait!"
echo -n "One Moment please "
while [ $sek -ge 1 ]
do
   echo -n "$sek" #print sek
   sleep 1
   sek=$[$sek-1] #update sek
   echo -en "\b\b\b" #'print' backtrace
done
echo
echo "ready!"
aneeshep
  • 30,321
  • 2
    Amazing, thanks a lot. Just one notice for the other readers. To fit the code above you have to use echo -en "\b\b\b" because of the space. – NES Dec 04 '10 at 12:10
  • 1
    +1 Nice, but... below 10 it starts eating the message... – lepe Jan 28 '11 at 03:51
  • 1
    Yep, better to use \r. See my answer. – Mikel Feb 03 '11 at 23:44
  • 3
    From bash's manual: The old format $[expression] is deprecated and will be removed in upcoming versions of bash.. Use the POSIX $((expression)) or the ((-command instead. E.g. sek=$(( sek - 1 )) or (( sek = sek - 1 )) or (( sek-- )). – geirha Feb 03 '11 at 23:49
8

With bash you can use the special variable SECONDS.

#BASH
SECONDS=0;
while sleep .5 && ((SECONDS <= 60)); do 
    printf '\r%s: %2d' "One moment please" "$((60-SECONDS))"
done
printf '\n'
geirha
  • 46,101
  • +1 Good answer, plus I never knew about SECONDS. – Mikel Feb 03 '11 at 23:47
  • It works, but it is a bit odd seeing 'sleep .5' as a tested while-loop condition.. Howerver I particularly like the use of $SECONDS, because it relates to real time, (ie. not a fixed elapsed time between time-consuming actions, as with sleep 1)... SECONDS This variable expands to the number of seconds since the shell was started. (so I probably wouldn't set it to 0.. just test it for 60 seconds more from when the script started) .. +1 – Peter.O Feb 04 '11 at 00:17
  • I'm commenting further, only because I'm personally interested in a way to get an accurate coutntdown... I've tested $SECONDS and it seems that whether it is set to '0' or not it is still dependant on the system's current fractional second.. ie. Setting it to '0' can result in a time of 0.99... (the same is true if it is left as-is).... So, its best average chance is to be within only .5 of a second... Just a comment (that's what comments are for :) – Peter.O Feb 04 '11 at 02:54
  • @fred.bear Well, you'll never really get it accurate; some other process could start hogging CPU and/or IO while the countdown is going and ruin whatever accuracy you had earlier. What you can do is to have it wait for at least X ammount of time, and give some rough countdown if desired. When a program says "it'll take a minute", do you expect it to take exactly 1 minute, to the millisecond? or to take 1 minute, give or take a few seconds? – geirha Feb 04 '11 at 03:06
  • @geirha... Yes, that variation won't matter in 99% of cases, and its great you've made me aware of $SECONDS... I'm just finding its limits... I've played with a variation of your script which reports the finishing time based on a .01 sec sleep ( plus, as you mentioned, any external system lag ) yet only printing when a second clicks over, but then I discovered the first 'second' was printing too soon ( in one case, at just after the first .01 sec ) .. It's all part of my bash learning curve... I like your answer, that's why I've had a deeper look at it. – Peter.O Feb 04 '11 at 03:25
  • I just re-read the question... It seems I've gone a bit off-track :) .. but I'm a bit more bash-aware now ... – Peter.O Feb 04 '11 at 03:35
5

In addition to \r or \b approaches, it's possible to use the \033[2K control character, which tells the terminal to clear the whole line. The advantage of this compared to \b is that you don't have to match number of \b with the amount of characters you want to delete, and compared to \r there won't be characters sticking out on the screen if the new line is shorter than the old one.

Below is the example of how it can be applied to this question, and here is an example of the related application to create output similar to boot messages. In this particular example, the timer will be gone once 0th second is reached and the timer line will be replaced with "Ready!" phrase.

#!/bin/bash
sek=60
echo "60 Seconds"

while ((sek--)); do
    printf "One moment please: %d" "$sek"
    sleep 1
    printf "\r%b" "\033[2K"
done
echo "Ready!"

Another alternative would be to employ dialog command for creating simple dialogs in command-line. The dialog will remain on screen for the duration of the timer and update with the loop, and by the time it's done - the timer will be replaced with "Ready! Press to exit" message in a seamless manner:

#!/bin/bash
sek=60
echo "60 Seconds"

while ((sek--)); do
    echo "$sek" | dialog --progressbox "Please wait" 10 25
    sleep 1
done
dialog --msgbox "Ready! Press <OK> to finish" 10 25
Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
1

Can achieve it by placing carriage return \r.

In a single line of code, it is possible with echo -ne

for i in {60..1}; do echo -ne "One moment please: $i\r" && sleep 1; done

or with printf

for i in {60..1}; do printf "One moment please: $i\r" && sleep 1; done
Akif
  • 133
1

This is what I came up with after reading here and a bit more, the one liner:

SEC=101;for i in `seq $SEC -1 1`;do printf "\rNext in: %`expr length $SEC`ds" "$i";sleep 1;done;echo

More readable:

#!/bin/bash
SEC=101

for i in `seq $SEC -1 1`;do
        printf "\rNext in: %`expr length $SEC`ds" "$i";
        sleep 1;
done
echo

Where SEC can be set to whatever positive integer and the printf will take care of appropriate padding. Tested under Ubuntu and cygwin.

Tod
  • 19
-2

Countdown timer:

MIN=1 && for i in $(seq $(($MIN*60)) -1 1); do echo -n "$i, "; sleep 1; done; echo -e "nnMessage"

and the 'normal' stopwatch is:

START=$( date +%s ); while true; do CURRENT=$( date +%s ) ; echo $(( CURRENT-START )) ; sleep 1 ; echo -n  ; done

control+c to stop

Habitual
  • 254