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 :))

1
  • There might be a way to do this with the watch command, although I'm not sure exactly how to do it. Sep 24, 2013 at 21:36

7 Answers 7

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!"
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!"
4
  • 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 4, 2010 at 12:10
  • 1
    +1 Nice, but... below 10 it starts eating the message...
    – lepe
    Jan 28, 2011 at 3:51
  • 1
    Yep, better to use \r. See my answer.
    – Mikel
    Feb 3, 2011 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 3, 2011 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'
6
  • +1 Good answer, plus I never knew about SECONDS.
    – Mikel
    Feb 3, 2011 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 4, 2011 at 0: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 4, 2011 at 2: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 4, 2011 at 3: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 4, 2011 at 3:25
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
1
  • dialog doesn't exists on mac :/
    – Ricky Levi
    Jun 5, 2019 at 11:12
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.

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
-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

1
  • This doesn't answer the question about overwriting text Oct 20, 2011 at 9:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .