260

How can I display a real-time countdown timer on the Linux terminal? Is there an existing app or, even better, a one liner to do this?

Breakthrough
  • 34,847
tir38
  • 2,841

35 Answers35

316

I'm not sure why you need beep. If all you want is a stopwatch, you can do this:

while true; do printf '%s\r' "$(date)"; done

That will show you the seconds passing in realtime and you can stop it with Ctrl+C. If you need greater precision, you can use this to give you nanoseconds:

while true; do printf '%s\r' "$(date +%H:%M:%S:%N)"; done

Finally, if you really, really want "stopwatch format", where everything starts at 0 and starts growing, you could do something like this:

start=$(date +%s)
while true; do
    time="$(($(date +%s) - $start))"
    printf '%s\r' "$(date -u -d "@$time" +%H:%M:%S)"
done

For a countdown timer (which is not what your original question asked for) you could do this (change seconds accordingly):

seconds=20
start="$(($(date +%s) + $seconds))"
while [ "$start" -ge `date +%s` ]; do
    time="$(( $start - `date +%s` ))"
    printf '%s\r' "$(date -u -d "@$time" +%H:%M:%S)"
done

You can combine these into simple commands by using bash (or whichever shell you prefer) functions. In bash, add these lines to your ~/.bashrc (the sleep 0.1 will make the system wait for 1/10th of a second between each run so you don't spam your CPU):

countdown() {
    start="$(( $(date '+%s') + $1))"
    while [ $start -ge $(date +%s) ]; do
        time="$(( $start - $(date +%s) ))"
        printf '%s\r' "$(date -u -d "@$time" +%H:%M:%S)"
        sleep 0.1
    done
}

stopwatch() { start=$(date +%s) while true; do time="$(( $(date +%s) - $start))" printf '%s\r' "$(date -u -d "@$time" +%H:%M:%S)" sleep 0.1 done }

You can then start a countdown timer of one minute by running:

countdown 60

You can countdown two hours with:

countdown "$((2 * 60 * 60))"

or a whole day using:

countdown "$((24 * 60 * 60))"

And start the stopwatch by running:

stopwatch

If you need to be able to deal with days as well as hours, minutes and seconds, you could do something like this:

countdown() {
    start="$(( $(date +%s) + $1))"
    while [ "$start" -ge $(date +%s) ]; do
        ## Is this more than 24h away?
        days="$(($(($(( $start - $(date +%s) )) * 1 )) / 86400))"
        time="$(( $start - `date +%s` ))"
        printf '%s day(s) and %s\r' "$days" "$(date -u -d "@$time" +%H:%M:%S)"
        sleep 0.1
    done
}

stopwatch() { start=$(date +%s) while true; do days="$(($(( $(date +%s) - $start )) / 86400))" time="$(( $(date +%s) - $start ))" printf '%s day(s) and %s\r' "$days" "$(date -u -d "@$time" +%H:%M:%S)" sleep 0.1 done }

Note that the stopwatch function hasn't been tested for days since I didn't really want to wait 24 hours for it. It should work, but please let me know if it doesn't.

terdon
  • 54,564
159

My favorite way is:

Start:

time cat

Stop:

ctrl+c

As @wjandrea commented below, another version is to run:

time read

and press Enter to stop

Nir Alfasi
  • 1,938
90

I was looking for the same thing and ended up writing something more elaborate in Python:

This will give you a simple 10-second countdown:

pip install termdown
termdown 10

Source: https://github.com/trehn/termdown

trebor
  • 163
trehn
  • 1,001
42

Use leave (it at least works on BSD descendants):

man leave

Set a timer for 15 minutes:

leave +0015
Alarm set for Thu Nov  3 14:19:31 CDT 2016. (pid 94317)
efk
  • 529
  • 4
  • 3
22

Short answer:

for i in `seq 60 -1 1` ; do echo -ne "\r$i " ; sleep 1 ; done

Explanation:

I know there are a lot of answers, but I just want to post something very close to OP's question, that personally I would accept as indeed "oneliner countdown in terminal". My goals were:

  1. One liner.
  2. Countdown.
  3. Easy to remember and type in console (no functions and heavy logic, bash only).
  4. Does not require additional software to install (can be used on any server I go via ssh, even if I do not have root there).

How it works:

  1. seq prints numbers from 60 to 1.
  2. echo -ne "\r$i " returns caret to beginning of the string and prints current $i value. Space after it is required to overwrite previous value, if it was longer by characters than current $i (10 -> 9).
cronfy
  • 371
14

I've used this one:

countdown()
(
  IFS=:
  set -- $*
  secs=$(( ${1#0} * 3600 + ${2#0} * 60 + ${3#0} ))
  while [ $secs -gt 0 ]
  do
    sleep 1 &
    printf "\r%02d:%02d:%02d" $((secs/3600)) $(( (secs/60)%60)) $((secs%60))
    secs=$(( $secs - 1 ))
    wait
  done
  echo
)

Example:

 countdown "00:07:55"

Here's a source.

Adobe
  • 3,000
9

sw is a simple stopwatch that will run forever.

sw

Install

wget -q -O - http://git.io/sinister | sh -s -- -u https://raw.githubusercontent.com/coryfklein/sw/master/sw

Usage

sw
 - start a stopwatch from 0, save start time in ~/.sw
sw [-r|--resume]
 - start a stopwatch from the last saved start time (or current time if no last saved start time exists)
 - "-r" stands for --resume
Cory Klein
  • 1,752
7

I use this small Go program:

package main

import ( "fmt" "time" )

func format(d time.Duration) string { mil := d.Milliseconds() % 1000 sec := int(d.Seconds()) % 60 min := int(d.Minutes()) return fmt.Sprintf("%v m %02v s %03v ms", min, sec, mil) }

func main() { t := time.Now() for { time.Sleep(10 * time.Millisecond) s := format(time.Since(t)) fmt.Print("\r", s) } }

https://github.com/89z/sienna/tree/master/cmd/stopwatch

Zombo
  • 1
7

Another approach

countdown=60 now=$(date +%s) watch -tpn1 echo '$((now-$(date +%s)+countdown))'

For Mac:

countdown=60 now=$(date +%s) watch -tn1 echo '$((now-$(date +%s)+countdown))'
#no p option on mac for watch

If one wants a signal when it hits zero, one could e.g. build it with a command that returned a non-zero exit status at zero and combine it with watch -b, or something, but if one wants to build a more elaborate script, this is probably not the way to go; it is more of a "quick and dirty one-liner" type solution.


I like the watch program in general. I first saw it after I had already written countless while sleep 5; do loops to different effects. watch was demonstrably nicer.

7

I have combined terdon's very good answer, into a function which at the same time displays the time since the start, and the time till the end. There are also three variants, so it's easier to call (you don't have to do Bash math), and it's also abstracted.

Example of use:

{ ~ }  » time_minutes 15
Counting to 15 minutes
Start at 11:55:34     Will finish at 12:10:34
     Since start: 00:00:08     Till end:  00:14:51

And something like a work timer:

{ ~ }  » time_hours 8
Counting to 8 hours
Start at 11:59:35   Will finish at 19:59:35
     Since start: 00:32:41     Till end:  07:27:19

And if you need some very specific time:

{ ~ }  » time_flexible 3:23:00
Counting to 3:23:00 hours
Start at 12:35:11   Will finish at 15:58:11
     Since start: 00:00:14     Till end:  03:22:46

Here's the code to put into your .bashrc

function time_func()
{
   date2=$((`date +%s` + $1));
   date1=`date +%s`;
   date_finish="$(date --date @$(($date2)) +%T )"

   echo "Start at `date +%T`   Will finish at $date_finish"

    while [ "$date2" -ne `date +%s` ]; do
     echo -ne "     Since start: $(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)     Till end:  $(date -u --date @$(($date2 - `date +%s`)) +%H:%M:%S)\r";
     sleep 1
    done

    printf "\nTimer finished!\n"
    play_sound ~/finished.wav
}

function time_seconds()
{
  echo "Counting to $1 seconds"
  time_func $1
}

function time_minutes()
{
  echo "Counting to $1 minutes"
  time_func $1*60
}

function time_hours()
{
  echo "Counting to $1 hours"
  time_func $1*60*60
}

function time_flexible()  # Accepts flexible input hh:mm:ss
{
    echo "Counting to $1"
    secs=$(time2seconds $1)
    time_func $secs
}

function play_sound()  # Adjust to your system
{
    cat $1 > /dev/dsp
}

function time2seconds() # Changes hh:mm:ss to seconds. Found in some other Stack Exchange answer
{ 
    a=( ${1//:/ }) 
    echo $((${a[0]}*3600+${a[1]}*60+${a[2]})) 
}

Combine this with some way of playing sound in linux terminal (Play MP3 or WAV file via the Linux command line) or Cygwin (cat /path/foo.wav > /dev/dsp works for me in Babun/Windows 7) and you have a simple flexible timer with alarm!

Koshmaar
  • 149
5

I'm surprised that nobody used the sleepenh tool in their scripts. Instead, the proposed solutions either use a sleep 1 between subsequent timer outputs or a busy loop that outputs as fast as possible. The former is inadequate because due to the small time spent doing the printing, the output will not actually happen once per second but a bit less than that which is suboptimal. After enough time passed, the counter will skip a second. The latter is inadequate because it keeps the CPU busy for no good reason.

The tool that I have in my $PATH looks like this:

#!/bin/sh
if [ $# -eq 0 ]; then
    TIMESTAMP=$(sleepenh 0)
    before=$(date +%s)
    while true; do
        diff=$(($(date +%s) - before))
        printf "%02d:%02d:%02d\r" $((diff/3600)) $(((diff%3600)/60)) $((diff%60))
        TIMESTAMP=$(sleepenh $TIMESTAMP 1.0);
    done
    exit 1 # this should never be reached
fi
echo "counting up to $@"
"$0" &
counterpid=$!
trap "exit" INT TERM
trap "kill 0" EXIT
sleep "$@"
kill $counterpid

The script can either be used as a stop watch (counting up until interrupted) or as a timer that runs for the specified amount of time. Since the sleep command is used, this script allows to specify the duration for which to count in the same precision as your sleep allows. On Debian and derivatives, this includes sub-second sleeps and a nice human-readable way to specify the time. So for example you can say:

$ time countdown 2m 4.6s
countdown 2m 4.6s  0.00s user 0.00s system 0% cpu 2:04.60 total

And as you can see, the command ran exactly for 2 minutes and 4.6 seconds without much magic in the script itself.

EDIT:

The sleepenh tool comes from the package of the same name in Debian and its derivatives like Ubuntu. For distributions that don't have it, it comes from https://github.com/nsc-deb/sleepenh

The advantage of sleepenh is, that it is able to take into account the small delay that accumulates over time from the processing of other things than the sleep during a loop. Even if one would just sleep 1 in a loop 10 times, the overall execution would take a bit more than 10 seconds because of the small overhead that comes from executing sleep and iterating the loop. This error slowly accumulates and would over time make our stopwatch timer more and more imprecise. To fix this problem, one must each loop iteration compute the precise time to sleep which is usually slightly less than a second (for one second interval timers). The sleepenh tool does this for you.

josch
  • 988
4

Take a look at TermTime. It's a nice terminal based clock and stopwatch:

pip install termtime
OdinX
  • 211
4

I'd like to suggest "timer" for a large countdown clock. It's similar to termdown but it's written in Rust and provides a nice beep at the end.

timer demo

Usage

timer 11:00
timer 25min

Install

cargo install timer_core

Source: https://github.com/pando85/timer

trebor
  • 163
3

If you have pv installed, you can use the following one-liner to display a countdown timer and sleep for a minute (or any other amount of time, just change 60 to the desired number of seconds):

cat /dev/zero | pv -B 1 -L 1 -tpe -s 60 -S > /dev/null

You can also put it in a function:

sleep_with_progress() {
  cat /dev/zero | pv -B 1 -L 1 -tpe -s "$1" -S > /dev/null
}

If you prefer a simpler output with just the countdown:

sleep_with_countdown() { cat /dev/zero | pv -B 1 -L 1 -e -s "$1" -S > /dev/null }

Sample outputs taken at 5 seconds after starting:

user@host:~ $ sleep_with_progress 60
0:00:05 [===>                                                  ]  8% ETA 0:00:55
user@host:~ $ sleep_with_countdown 60
ETA 0:00:55

Explanation:

cat /dev/zero produces an infinite amount of ASCII zero (\0) characters. pv displays progress, rate limits the data flowing through it and terminates after 60 characters (details below). Finally, the redirection to /dev/null makes sure that the \0 characters are not sent to the terminal.

The parameters used for pv are:

  • -B 1 sets the buffer size to 1.
  • -L 1 rate limits the pipe to 1 character per second.
  • -tpe turns on the display of the time, progress and ETA indicators (while -e only shows the latter).
  • -s 60 specifies that pv should expect 60 bytes.
  • -S tells pv to stop after reaching the specified size even though the input continues (it is infinite).
Zoltan
  • 249
  • 2
  • 10
3

I ended up writing my own shell script: GitHub gist

#!/bin/sh
# script to create timer in terminal
# Jason Atwood
# 2013/6/22

# Start up
echo "starting timer script ..."
sleep 1 # seconds

# Get input from user
read -p "Timer for how many minutes?" -e DURATION
DURATION=$(( $DURATION*60 )) # convert minutes to seconds

# Get start time
START=$(date +%s)

# Infinite loop
while [ -1 ]; do
clear # Clear window

# Do math
NOW=$(date +%s)    # Get time now in seconds
DIF=$(( $NOW-$START ))    # Compute diff in seconds
ELAPSE=$(( $DURATION-$DIF ))    # Compute elapsed time in seconds
MINS=$(( $ELAPSE/60 ))    # Convert to minutes... (dumps remainder from division)
SECS=$(( $ELAPSE - ($MINS*60) )) # ... and seconds

# Conditional
if [ $MINS == 0 ] && [ $SECS == 0 ]    # if mins = 0 and secs = 0 (i.e. if time expired)
then # Blink screen
for i in `seq 1 180`; # for i = 1:180 (i.e. 180 seconds)
do
clear # Flash on
setterm -term linux -back red -fore white # use setterm to change background color
echo "00:00 " # extra tabs for visibility

sleep 0.5

clear # Flash off
setterm -term linux -default # Clear setterm changes from above
echo "00:00" # (I.e. go back to white text on black background)
sleep 0.5
done # End for loop
break    # End script

else # Else, time is not expired
echo "$MINS:$SECS"    # Display time
sleep 1 # Sleep 1 second
fi    # End if
done    # End while loop
tir38
  • 2,841
2

just a one liner

N=100; while [[ $((--N)) >  0 ]]; do  echo  $N |  figlet -c && sleep 1 ; done

screenhost

enter image description here

Also we can clear the screen ( Terminal ) using ANSI Escape sequences using 2J format.

N=100; while [[ $((--N)) >  0 ]]; do  echo -e "\033[2J\033[0m"; echo "$N" |  figlet -c && sleep 1 ; done

NOTE

installing figlet command is required if you need in BIG font, otherwise remove figlet part.

N=100; while [[ $((--N)) >  0 ]]; do  echo  $N  && sleep 1 ; done

and you can make to have a beautiful output using lolcat ...

N=100; while [[ $((--N)) >  0 ]]; do  echo "$N" |  figlet -c | lolcat &&  sleep 1 ; done

screenhsot

enter image description here

Shakiba Moshiri
  • 289
  • 3
  • 9
2

For future reference, there is a command line tool called µTimer with very straightforward command line options for a countdown/count-up timer.

Tom
  • 353
2

Simply use watch + date in UTC time. You can also install some package for big display...

export now="`date +%s -u`";
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now ))'

#Big plain characters
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now )) | toilet -f mono12'

#Big empty charaters
watch -n 0,1 'date +%T -u -d @$((`date +%s` - $now )) | figlet -c -f big'

Try it!

See also http://www.cyberciti.biz/faq/create-large-colorful-text-banner-on-screen/

1

If your system already has Ruby, you can use:

#!/usr/bin/env ruby

n = ARGV[0].to_f go_beginning_of_line = "\033[G" clear_till_end_of_line = "\033[K"

time_end = Time.now + n while Time.now < time_end more = time_end - Time.now print "#{go_beginning_of_line}Counting down #{more.round(1)} of #{n} seconds#{clear_till_end_of_line}" sleep [0.1, more].min end puts "#{go_beginning_of_line}Counting down #{n} seconds. \a\aDone.#{clear_till_end_of_line}"

nonopolarity
  • 9,886
1

This is a terminal based timer: https://github.com/naa-7/terminal_work_timer

timer counting down timer stopped

naa-7
  • 11
  • 1
1

Simple one liner to do the countdown job:

cnt=10;until [ $cnt -eq 0 ]; do printf "\rYour Quiz will start in $cnt seconds.... ";sleep 1;cnt=$(expr $cnt - 1);done;echo;

This one liner is taken from my open source project called Automated_Quiz, which is hosted here : https://sourceforge.net/projects/automated-quiz/

For Stopwatch :

Paste the following three lines one by one on the terminal and press enter. Press Ctrl+c on the second line to exit the stopwatch, as required.

function stopwatch() {  cntd=$1; printf '%dd:%dh:%dm:%ds\n' $((cntd/86400)) $((cntd%86400/3600)) $((cntd%3600/60)) $((cntd%60)) ; };

cnt=0;while true; do printf "\rStopwatch : $cnt seconds";export var=$cnt;sleep 1;cnt=$(expr $cnt + 1);done;echo;

stopwatch $var

Joseph Quinsey
  • 597
  • 1
  • 10
  • 26
1

I added a progress bar to and unlied the variable names in terdon's answer:

countdown() {                                                                   
  stop="$(( $(date '+%s') + $1))"                                               
  term_width=$(tput cols)                                                       
  counter_width=10                                                              
  while [ $stop -ge $(date +%s) ]; do                                           
    delta="$(( $stop - $(date +%s) ))"                                          
    complete_percent=$(( 100 - ($delta * 100) / $1))                            
    bar_width=$(($complete_percent * ($term_width - $counter_width) / 100))     
    printf '\r'                                                                 
    printf '%s ' "$(date -u -d "@$delta" +%H:%M:%S)"                            
    printf '%0.s-' $(seq 1 $bar_width)                                          
    sleep 0.5                                                                   
  done                                                                          
  printf '\n'                                                                   
}                 

It outputs something like:


00:00:05 --------------------
Rovanion
  • 255
  • 1
  • 2
  • 13
1

A Python example:

#!/usr/bin/python

def stopwatch ( atom = .01 ):
    import time, sys, math

    start = time.time()
    last = start
    sleep = atom/2
    fmt = "\r%%.%sfs" % (int(abs(round(math.log(atom,10))))  if atom<1 else "")
    while True:
        curr = time.time()
        subatom = (curr-last)
        if subatom>atom:
            # sys.stdout.write( "\r%.2fs" % (curr-start))
            sys.stdout.write( fmt % (curr-start))
            sys.stdout.flush()
            last = curr
        else:
            time.sleep(atom-subatom)

stopwatch()

demo

user84207
  • 463
1

Pretend you are a person on OS X looking for a command line stopwatch. Pretend that you don't want to install the gnu tools and just want to run with the Unix date.

In that case do as terdon says, but with this modification:

function stopwatch(){
    date1=`date +%s`;
    while true; do
        echo -ne "$(date -jf "%s" $((`date +%s` - $date1)) +%H:%M:%S)\r";
        sleep 0.1
    done
}
joem
  • 111
0

This is similar to the accepted answer, but terdon's countdown() gave me syntax errors. This one works great for me, though:

function timer() { case "$1" in -s) shift;; *) set $(($1 * 60));; esac; local S=" "; for i in $(seq "$1" -1 1); do echo -ne "$S\r $i\r"; sleep 1; done; echo -e "$S\rTime's up!"; }

You can put it in .bashrc and then execute with: timer t (where t is time in minutes).

0

Found this question earlier today, when looking for a term application to display a large countdown timer for a workshop. None of the suggestions was exactly what I needed, so I quickly put another one together in Go: https://github.com/bnaucler/cdown

As the question is already sufficiently answered, consider this to be for the sake of posterity.

0

$ sleep 1500 && xterm -fg yellow -g 240x80 &

When that big terminal with yellow text jumps up, time to get up and stretch!

Notes: - 1500 seconds = 25 minute pomodoro - 240x80 = terminal size with 240 character row, and 80 rows. Fills up a screen for me noticeably.

Credit: http://www.linuxquestions.org/questions/linux-newbie-8/countdown-timer-for-linux-949463/

user79878
  • 251
0

A GUI version of the stopwatch

date1=`date +%s`
date1_f=`date +%H:%M:%S____%d/%m`
(
  while true; do 
    date2=$(date -u --date @$((`date +%s` - $date1)) +%H:%M:%S)
    echo "# started at $date1_f \n$date2"
  done
) |
zenity --progress \
  --title="Stop Watch" \
  --text="Stop Watch..." \
  --percentage=0
mms
  • 125
0

Another option under the "existing app" category: peaclock. Displays a "large" stopwatch, timer, or clock in the terminal. Quite a few key bindings facilitate interactive use.

0

A slightly shorter Python (3) example:

import sys
import time
from datetime import datetime
from datetime import timedelta

start = time.time() * 1000 while True: try: now = time.time() * 1000 elapsed = now - start out = str(timedelta(milliseconds=elapsed)) print(out + "\033[?25l", end='\r') except KeyboardInterrupt: print(out + '\033[?25h', end='\n') sys.exit(0)

Hides / restores blinking cursor. Control-c to quit.

0

Here's a dash script (Linux/Mac OS compatible) that implements (displayed as Menu Options when starting the script):

  1. An "active/pause" stopwatch
  2. A count up timer
  3. A countdown timer
#!/bin/dash
## Supported shells: dash, bash, zsh, ksh

PrintInTitle () { printf "\033]0;%s\007" "$1" }

PrintJustInTitle () { PrintInTitle "$1">"$print_to_screen" }

GetSecondsSinceEpoch () { gsse_seconds_since_epoch="$(date '+%s')" eval $1="$gsse_seconds_since_epoch" }

TimerStart () { GetSecondsSinceEpoch ts_timer_seconds_since_epoch eval ts_input_timer_seconds_since_epoch=&quot;$$1_start_time_seconds_since_epoch&quot; eval ts_extra_time=&quot;$$1_extra_time_in_seconds&quot;

if [ ! &quot;$ts_input_timer_seconds_since_epoch&quot; = &quot;-1&quot; ]; then
    if [ -z &quot;$ts_input_timer_seconds_since_epoch&quot; ] || [ &quot;$ts_input_timer_seconds_since_epoch&quot; = &quot;0&quot; ]; then
        eval $1\_start_time_seconds_since_epoch=$ts_timer_seconds_since_epoch
        eval $1\_extra_time_in_seconds=&quot;0&quot;
    else
        eval $1\_start_time_seconds_since_epoch=$(( $ts_input_timer_seconds_since_epoch ))
        eval $1\_extra_time_in_seconds=&quot;0&quot;
    fi
else
    eval $1\_start_time_seconds_since_epoch=$(( $ts_timer_seconds_since_epoch ))
    eval $1\_extra_time_in_seconds=&quot;$ts_extra_time&quot;
fi

}

TimerGetElapsedTimeInSeconds () { GetSecondsSinceEpoch tgetis_seconds_since_epoch

eval tgetis_input_timer_start_time_seconds_since_epoch=\&quot;\$$1\_start_time_seconds_since_epoch\&quot;
if [ -z &quot;$tgetis_input_timer_start_time_seconds_since_epoch&quot; ] || [ &quot;$tgetis_input_timer_start_time_seconds_since_epoch&quot; = &quot;-1&quot; ]; then
    tgetis_input_timer_start_time_seconds_since_epoch=$(( tgetis_seconds_since_epoch ))
fi

eval tgetis_extra_time=\&quot;\$$1\_extra_time_in_seconds\&quot;
if [ -z &quot;$tgetis_extra_time&quot; ]; then
    tgetis_extra_time=&quot;0&quot;
fi
tgetis_elapsed_time_in_seconds=$(( $tgetis_seconds_since_epoch - $tgetis_input_timer_start_time_seconds_since_epoch + $tgetis_extra_time ))
eval $2=$(( $tgetis_elapsed_time_in_seconds ))

}

TimerSetTimeInSecondsSinceEpoch () { time_to_set_in_seconds=$(( $2 )) eval $1_start_time_seconds_since_epoch="&quot;$time_to_set_in_seconds&quot;" }

TimerSetTimeFullTime () { time_to_set_full_time="$2" GetSecondsSinceEpoch tstft_seconds_since_epoch ConvertFullTimeToSeconds time_to_set_full_time time_to_set_in_seconds TimerSetTimeInSecondsSinceEpoch $1 $(( $tstft_seconds_since_epoch - $time_to_set_in_seconds )) }

TimerPause () { TimerGetElapsedTimeInSeconds $1 tp_timer_elapsed_time_in_seconds eval $1_start_time_seconds_since_epoch="-1" eval tp_timer_previous_extra_time_in_seconds=&quot;$$1_extra_time_in_seconds&quot; eval $1_extra_time_in_seconds=$(( $tp_timer_elapsed_time_in_seconds )) }

ReGenerateFullTime () { eval rgft_input_time=&quot;$$1&quot; rgft_processed_input_time=$(printf '%s' "$rgft_input_time"|sed 's/[\ \t]//g; s/D/d/g; s/H/h/g; s/M/m/g; s/S/s/g') eval $2=&quot;$rgft_processed_input_time&quot; }

ConvertTimeToFullTime () { eval cttft_input_time=&quot;$$1&quot;

if [ &quot;$cttft_input_time&quot; = &quot;0&quot; ] || [ -z &quot;$cttft_input_time&quot; ]; then
    cttft_full_time=&quot;0s&quot;
else

    ConvertFullTimeToSeconds cttft_input_time cttft_input_time_in_seconds

    cttft_full_time=&quot;&quot;

    days=$(( $cttft_input_time_in_seconds / (24 * 60 * 60) ));
    cttft_input_time_in_seconds_minus_days=$(( $cttft_input_time_in_seconds - $days * (24 * 60 * 60) ))
    hours=$(( $cttft_input_time_in_seconds_minus_days / (60 * 60) ));
    cttft_input_time_in_seconds_minus_days_hours=$(( $cttft_input_time_in_seconds_minus_days - $hours * (60 * 60) ))
    minutes=$(( $cttft_input_time_in_seconds_minus_days_hours / (60) ));
    cttft_input_time_in_seconds_minus_days_hours_minutes=$(( $cttft_input_time_in_seconds_minus_days_hours - $minutes * (60) ))
    seconds=$(( $cttft_input_time_in_seconds_minus_days_hours_minutes ))

    found_seconds=&quot;false&quot;
    found_minutes=&quot;false&quot;
    found_hours=&quot;false&quot;
    found_days=&quot;false&quot;

    if [ &quot;$days&quot; -gt &quot;0&quot; ]; then
        days=&quot;$days&quot;&quot;d&quot;
        found_days=&quot;true&quot;
    fi
    if [ &quot;$hours&quot; -gt &quot;0&quot; ]; then
        hours=&quot;$hours&quot;&quot;h&quot;
        found_hours=&quot;true&quot;
    fi
    if [ &quot;$minutes&quot; -gt &quot;0&quot; ]; then
        minutes=&quot;$minutes&quot;&quot;m&quot;
        found_minutes=&quot;true&quot;
    fi
    if [ &quot;$seconds&quot; -gt &quot;0&quot; ]; then
        seconds=&quot;$seconds&quot;&quot;s&quot;
        found_seconds=&quot;true&quot;
    fi

    if [ &quot;$found_days&quot; = &quot;true&quot; ]; then
        cttft_full_time=&quot;$days&quot;&quot; &quot;
    fi
    if [ &quot;$found_hours&quot; = &quot;true&quot; ]; then
        cttft_full_time=&quot;$cttft_full_time$hours&quot;&quot; &quot;
    fi
    if [ &quot;$found_minutes&quot; = &quot;true&quot; ]; then
        cttft_full_time=&quot;$cttft_full_time$minutes&quot;&quot; &quot;
    fi
    if [ &quot;$found_seconds&quot; = &quot;true&quot; ]; then
        cttft_full_time=&quot;$cttft_full_time$seconds&quot;&quot; &quot;
    fi
    cttft_full_time=&quot;${cttft_full_time%&quot; &quot;}&quot;
fi

eval $2=&quot;\&quot;\$cttft_full_time\&quot;&quot;

}

ConvertFullTimeToSeconds () { eval cftts_input_time_full_time="&quot;$$1&quot;"

ReGenerateFullTime cftts_input_time_full_time cftts_input_time_full_time

processed_cftts_input_time_full_time=&quot;$cftts_input_time_full_time&quot;

if [ ! &quot;${processed_cftts_input_time_full_time%&quot;d&quot;*}&quot; = &quot;$processed_cftts_input_time_full_time&quot; ]; then
    days=&quot;${processed_cftts_input_time_full_time%&quot;d&quot;*}&quot;
    while [ ! &quot;${days}&quot; = &quot;${days#&quot; &quot;}&quot; ]; do days=&quot;${days#&quot; &quot;}&quot;; done; if [ -z &quot;$days&quot; ]; then days=&quot;0&quot;; fi #Remove ' '(space)-padding from days
    while [ ! &quot;${days}&quot; = &quot;${days#&quot;0&quot;}&quot; ]; do days=&quot;${days#&quot;0&quot;}&quot;; done; if [ -z &quot;$days&quot; ]; then days=&quot;0&quot;; fi #Remove '0'-padding from days

    [ &quot;$days&quot; -eq &quot;$days&quot; ] 2&gt;/dev/null||{
        printf '%s' &quot;ERROR: Invalid full time format (expected something like: 1d 2h 10m 30s). Press Enter to exit...&quot;
        read temp
        exit 1
    }&gt;&amp;2
    processed_cftts_input_time_full_time=&quot;${processed_cftts_input_time_full_time#&quot;$days&quot;&quot;d&quot;}&quot;
else
    days=&quot;0&quot;
fi

if [ ! &quot;${processed_cftts_input_time_full_time%&quot;h&quot;*}&quot; = &quot;$processed_cftts_input_time_full_time&quot; ]; then
    hours=&quot;${processed_cftts_input_time_full_time%&quot;h&quot;*}&quot;
    while [ ! &quot;${hours}&quot; = &quot;${hours#&quot; &quot;}&quot; ]; do hours=&quot;${hours#&quot; &quot;}&quot;; done; if [ -z &quot;$hours&quot; ]; then hours=&quot;0&quot;; fi #Remove ' '(space)-padding from hours
    while [ ! &quot;${hours}&quot; = &quot;${hours#&quot;0&quot;}&quot; ]; do hours=&quot;${hours#&quot;0&quot;}&quot;; done; if [ -z &quot;$hours&quot; ]; then hours=&quot;0&quot;; fi #Remove '0'-padding from hours

    [ &quot;$hours&quot; -eq &quot;$hours&quot; ] 2&gt;/dev/null||{
        printf '%s' &quot;ERROR: Invalid full time format (expected something like: 1d 2h 10m 30s). Press Enter to exit...&quot;
        read temp
        exit 1
    }&gt;&amp;2
    processed_cftts_input_time_full_time=&quot;${processed_cftts_input_time_full_time#&quot;$hours&quot;&quot;h&quot;}&quot;
else
    hours=&quot;0&quot;
fi

if [ ! &quot;${processed_cftts_input_time_full_time%&quot;m&quot;*}&quot; = &quot;$processed_cftts_input_time_full_time&quot; ]; then
    minutes=&quot;${processed_cftts_input_time_full_time%&quot;m&quot;*}&quot;
    while [ ! &quot;${minutes}&quot; = &quot;${minutes#&quot; &quot;}&quot; ]; do minutes=&quot;${minutes#&quot; &quot;}&quot;; done; if [ -z &quot;$minutes&quot; ]; then minutes=&quot;0&quot;; fi #Remove ' '(space)-padding from minutes
    while [ ! &quot;${minutes}&quot; = &quot;${minutes#&quot;0&quot;}&quot; ]; do minutes=&quot;${minutes#&quot;0&quot;}&quot;; done; if [ -z &quot;$minutes&quot; ]; then minutes=&quot;0&quot;; fi #Remove '0'-padding from minutes

    [ &quot;$minutes&quot; -eq &quot;$minutes&quot; ] 2&gt;/dev/null||{
        printf '%s' &quot;ERROR: Invalid full time format (expected something like: 1d 2h 10m 30s). Press Enter to exit...&quot;
        read temp
        exit 1
    }&gt;&amp;2
    processed_cftts_input_time_full_time=&quot;${processed_cftts_input_time_full_time#&quot;$minutes&quot;&quot;m&quot;}&quot;
else
    minutes=&quot;0&quot;
fi

seconds=&quot;${processed_cftts_input_time_full_time%&quot;s&quot;*}&quot;
while [ ! &quot;${seconds}&quot; = &quot;${seconds#&quot; &quot;}&quot; ]; do seconds=&quot;${seconds#&quot; &quot;}&quot;; done; if [ -z &quot;$seconds&quot; ]; then seconds=&quot;0&quot;; fi #Remove ' '(space)-padding from seconds
while [ ! &quot;${seconds}&quot; = &quot;${seconds#&quot;0&quot;}&quot; ]; do seconds=&quot;${seconds#&quot;0&quot;}&quot;; done; if [ -z &quot;$seconds&quot; ]; then seconds=&quot;0&quot;; fi #Remove '0'-padding from seconds

[ &quot;$seconds&quot; -eq &quot;$seconds&quot; ] 2&gt;/dev/null||{
    printf '%s' &quot;ERROR: Invalid full time format (expected something like: 1d 2h 10m 30s). Press Enter to exit...&quot;
    read temp
    exit 1
}&gt;&amp;2
if [ ! &quot;${processed_cftts_input_time_full_time%&quot;s&quot;*}&quot; = &quot;$processed_cftts_input_time_full_time&quot; ]; then
    processed_cftts_input_time_full_time=&quot;${processed_cftts_input_time_full_time#&quot;$seconds&quot;&quot;s&quot;}&quot;
else
    if [ -z &quot;$processed_cftts_input_time_full_time&quot; ]; then processed_cftts_input_time_full_time=&quot;0&quot;; fi
    seconds=&quot;$(( $processed_cftts_input_time_full_time ))&quot;
fi

output_time_in_seconds=$(( $days * (24 * 60 * 60) + $hours * (60 * 60) + $minutes * (60) + $seconds ));

eval $2=&quot;\&quot;\$output_time_in_seconds\&quot;&quot;

}

trap1 () {

TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
ConvertTimeToFullTime t1_elapsed_time_in_seconds t1_elapsed_time_full_time
TimerGetElapsedTimeInSeconds t2 t2_elapsed_time_in_seconds
ConvertTimeToFullTime t2_elapsed_time_in_seconds t2_elapsed_time_full_time
echo
echo &quot;Exited: Elapsed time: $t1_elapsed_time_full_time | Pause time: $t2_elapsed_time_full_time&quot;
echo

#kill all children processes, suppressing &quot;Terminated&quot; message:
kill -s PIPE -- -$$ 2&gt;/dev/null

exit

}

trap2 () {

echo
echo &quot;Exited: Elapsed time: $t1_elapsed_time_full_time / $time_to_wait&quot;
echo

#kill all children processes, suppressing &quot;Terminated&quot; message:
kill -s PIPE -- -$$ 2&gt;/dev/null

exit

}

trap3 () {

echo
echo &quot;Exited: Remaining time: $remaining_time_full_time / $time_to_wait&quot;
echo

#kill all children processes, suppressing &quot;Terminated&quot; message:
kill -s PIPE -- -$$ 2&gt;/dev/null

exit

}

\// START PROGRAM HERE: \//

print_to_screen='/dev/tty'

echo "Please choose an option and press Enter (default=1):" echo " &quot;1&quot; for &quot;active/pause&quot; stopwatch" echo " &quot;2&quot; for countup timer" echo " &quot;3&quot; for countdown timer" read option if [ -z "$option" ]; then option="1" fi

if [ "$option" = "1" ]; then #Trap "INTERRUPT" (CTRL/Control + C) and "TERMINAL STOP" (CTRL/Control + Z) signals: trap 'trap1' INT trap 'trap1' TSTP

#start_time=&quot;1D 23h 59M 58s&quot;
#TimerSetTimeFullTime t1 &quot;$start_time&quot;

while [ &quot;1&quot; = &quot;1&quot; ]; do

    TimerStart t1

    TimerGetElapsedTimeInSeconds t2 t2_elapsed_time_in_seconds
    ConvertTimeToFullTime t2_elapsed_time_in_seconds t2_elapsed_time_full_time

    {
        while [ &quot;1&quot; = &quot;1&quot; ]; do
            TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
            ConvertTimeToFullTime t1_elapsed_time_in_seconds t1_elapsed_time_full_time
            PrintJustInTitle &quot;Elapsed time: $t1_elapsed_time_full_time | Pause time: $t2_elapsed_time_full_time&quot;
            sleep 0.5
        done
    } &amp;
    bg_PID1=&quot;$!&quot;
    printf '%s' &quot;Press Enter to pause timer...&quot;
    read temp
    kill $bg_PID1
    TimerPause t1

    TimerStart t2

    TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
    ConvertTimeToFullTime t1_elapsed_time_in_seconds t1_elapsed_time_full_time

    {
        while [ &quot;1&quot; = &quot;1&quot; ]; do
            TimerGetElapsedTimeInSeconds t2 t2_elapsed_time_in_seconds
            ConvertTimeToFullTime t2_elapsed_time_in_seconds t2_elapsed_time_full_time
            PrintJustInTitle &quot;Elapsed time: $t1_elapsed_time_full_time | Pause time: $t2_elapsed_time_full_time&quot;
            sleep 0.5
        done
    } &amp;
    bg_PID2=&quot;$!&quot;
    printf '%s' &quot;Press Enter to restart timer...&quot;
    read temp
    kill &quot;$bg_PID2&quot;

    TimerPause t2
done

elif [ "$option" = "2" ]; then

#Trap &quot;INTERRUPT&quot; (CTRL/Control + C) and &quot;TERMINAL STOP&quot; (CTRL/Control + Z) signals:
trap 'trap2' INT
trap 'trap2' TSTP

printf &quot;Please provide time to wait (e.g.: 10m):&quot;
read time_to_wait
ConvertFullTimeToSeconds time_to_wait time_to_wait_in_seconds
ConvertTimeToFullTime time_to_wait_in_seconds time_to_wait

TimerStart t1

TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds

while [ ! &quot;$t1_elapsed_time_in_seconds&quot; -gt &quot;$time_to_wait_in_seconds&quot; ]; do
    TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
    ConvertTimeToFullTime t1_elapsed_time_in_seconds t1_elapsed_time_full_time
    PrintJustInTitle &quot;Elapsed time: $t1_elapsed_time_full_time / $time_to_wait&quot;
    sleep 0.5
done
echo Done.

elif [ "$option" = "3" ]; then

#Trap &quot;INTERRUPT&quot; (CTRL/Control + C) and &quot;TERMINAL STOP&quot; (CTRL/Control + Z) signals:
trap 'trap3' INT
trap 'trap3' TSTP

printf &quot;Please provide time to wait (e.g.: 10m):&quot;
read time_to_wait
ConvertFullTimeToSeconds time_to_wait time_to_wait_in_seconds
ConvertTimeToFullTime time_to_wait_in_seconds time_to_wait

TimerStart t1

TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
remaining_time_in_seconds=$(( $time_to_wait_in_seconds - t1_elapsed_time_in_seconds ))


while [ &quot;$remaining_time_in_seconds&quot; -gt &quot;0&quot; ]; do
    TimerGetElapsedTimeInSeconds t1 t1_elapsed_time_in_seconds
    remaining_time_in_seconds=$(( $time_to_wait_in_seconds - t1_elapsed_time_in_seconds ))

    ConvertTimeToFullTime remaining_time_in_seconds remaining_time_full_time
    PrintJustInTitle &quot;Remaining time: $remaining_time_full_time / $time_to_wait&quot;
    sleep 0.5
done
echo Done.

else printf "Invalid option! Press Enter to exit..." read temp; fi

Note: In case of using the konsole terminal emulator - in order to be able to display a customized (personal) terminal emulator window title - it is required one initial additional step:

Konsole -> Settings -> Configure Konsole ... ->
-> Enable option "Show window title on the titlebar"
I. Marin
  • 1
  • 1
0

Using the top answer, I created the following version that supports any precision:

function stopwatch {
    local precision="${1:-3}"
    if [ "$precision" -eq 0 ]; then
        start="$(gdate '+%s')"
    else
        start="$(gdate "+%s%${precision}N")"
    fi
    while true; do
        if [ "$precision" -eq 0 ]; then
            now="$(gdate '+%s')"
            time="$((now - start))"
            printf "%s\r" "$(gdate -u -d "@$time" '+%H:%M:%S')"
        else
            now="$(gdate "+%s%${precision}N")"
            time="$((now - start))"
            seconds="$((time / 10**precision))"
            subseconds="$((time % 10**precision))"
            printf "%s.%0${precision}d\r" "$(gdate -u -d "@$seconds" '+%H:%M:%S')" "$subseconds"
        fi
    done
}
HappyFace
  • 1,389
0

Countdown (30 seconds):

start="$(( $(date '+%s') + 30))"; watch -tn1 echo '$(('$start' - $(date +%s)))';

Stopwatch:

start="$(( $(date '+%s')))"; watch -tn1 echo '$(($(date +%s) - '$start'))';

Countdown that stops at zero and also beeps a sound when done (3 seconds):

start="$(( $(date '+%s') + 3))"; watch -betn1 \
"r=\$(($start - \$(date +%s))); echo \$r; test \$r -gt 0 || exit 1"
0

This used to be a recurring problem for me until I made a solution that worked for me. So, although there are already many answers, I would like to share my solution.

Here is a gist of the full script:

https://gist.github.com/davidsusu/ce3029ee36c82077c50e84d19b9fb413

This is the basic idea:

export LC_NUMERIC="POSIX"
start="$( date +'%s.%N' )"
while true; do
    sleep 0.095
    time="$( date +'%s.%N' )"
    printf '\33[2K\r';
    seconds="$( echo "${start} ${time}" | awk '{printf "%f", $2 - $1}' )"
    echo -n "$( date -d@"${seconds}" -u +"%H:%M:%S.%1N" )"
done
-2

If you would like a compilable program for whatever reason, the following would work:

#include <iostream>
#include <string>
#include <chrono>

int timer(seconds count) {
  auto t1 = high_resolution_clock::now();
  auto t2 = t1+count;
  while ( t2 > high_resolution_clock::now()) {
    std::cout << "Seconds Left:" <<
    std::endl <<
      duration_cast<duration<double>>(count-(high_resolution_clock::now()-t1)).count() <<
    std::endl << "\033[2A\033[K";
    std::this_thread::sleep_for(milliseconds(100));
  }
  std::cout << "Finished" << std::endl;
  return 0;
}

This can be used in other programs as well and easily ported, if a Bash environment isn't available or you just prefer using a compiled program.

GitHub

elder4222
  • 117