lewiatan
lewiatan

Reputation: 1176

How can I create a stopwatch in bash?

I created a simple stopwatch (bash function) for counting time, but for now it's showing current time with milliseconds.

The code:

function stopwatch() {
    date +%H:%M:%S:%N
    while true; do echo -ne "`date +%H:%M:%S:%N`\r"; done;
}

I tried to change it as explained in this answer, but it works only with second since Unix Epoch.

When I used date format +%s.%N the subtraction from the answer above stopped working due to the fact that bash subtraction takes only integer.

How can I solve it and have a terminal stopwatch that prints time like so:

0.000000000
0.123123123
0.435345345
(and so on..)

?

Upvotes: 19

Views: 13812

Answers (7)

Keith
Keith

Reputation: 673

Here's a nicer function I grabbed a while ago:

function stopwatch() {
    local BEGIN=$(date +%s)
    echo Starting Stopwatch...

    while true; do
        local NOW=$(date +%s)
        local DIFF=$(($NOW - $BEGIN))
        local MINS=$(($DIFF / 60))
        local SECS=$(($DIFF % 60))
        local HOURS=$(($DIFF / 3600))
        local DAYS=$(($DIFF / 86400))

        printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
        sleep 0.5
    done
}

In response to a comment, here's a version that will exit once the user presses the Enter key:

function stopwatch_with_cancel() {
    local BEGIN=$(date +%s)
    echo Starting Stopwatch...

    while true; do
        local NOW=$(date +%s)
        local DIFF=$(($NOW - $BEGIN))
        local MINS=$(($DIFF / 60))
        local SECS=$(($DIFF % 60))
        local HOURS=$(($DIFF / 3600))
        local DAYS=$(($DIFF / 86400))

        printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
        read -rsN1 -t1 key
        if [ "$key" == $'\x0a' ] ;then
            # echo -e "\n [Enter] Pressed"
            break
        fi
    done
}

Upvotes: 4

user3064538
user3064538

Reputation:

time cat

then press Ctrl-c or Ctrl-d to stop the timer and show the time. The first number is the time.

I've further refined it into this bash alias

alias stopwatch="echo Press Ctrl-c to stop the timer; TIMEFORMAT=%R; time cat; unset TIMEFORMAT"

Upvotes: 12

anishsane
anishsane

Reputation: 20980

One possible (& hacky) mechanism that can work for a day:

$ now=$(date +%s)sec
$ while true; do
     printf "%s\r" $(TZ=UTC date --date now-$now +%H:%M:%S.%N)
     sleep 0.1
  done

Bonus: You can press enter at any time to get the LAP times. ;-)

Note: This is a quick fix. Better solutions should be available...

watch based variant (same logic):

$ now=$(date +%s)sec; watch -n0.1 -p TZ=UTC date --date now-$now +%H:%M:%S.%N

Upvotes: 23

Carl Smith
Carl Smith

Reputation: 728

Here is another take on a bash stopwatch, drawing much from other answers in this thread. Ways in which this version differs from the others include:

  • This version uses bash arithmetic rather than calling bc which I found (by timing it) to be way less cpu time.
  • I have addressed the 25th-hour limitation that someone had pointed out by tacking 24 hours onto the hour part for every day elapsed. (So now I guess it's the ~31st-day limitation.)
  • I leave the cursor just to the right of the output, unlike the version in the accepted answer. That way you can easily measure laps (or more generally mark important event times) just by hitting enter, which will move the timer to the next line, leaving the time at keypress visible.
#!/bin/bash

start_time=$(date +%s)

while true; do
  current_time=$(date +%s)
  seconds_elapsed=$(( $current_time - $start_time ))
  timestamp=$(date -d"@$seconds_elapsed" -u +%-d:%-H:%-M:%-S)

  IFS=':' read -r day hour minute second <<< "$timestamp"
  hour="$(( $hour+24*($day-1) ))"

  printf "\r%02d:%02d:%02d" $hour $minute $second
  sleep 0.5
done;

Here is sample output from running stopwatch (as an executable script in the PATH) and hitting the return key at 7 and 18 seconds, and hitting Ctrl-C after about 9 minutes:

$ stopwatch
00:00:07
00:00:18
00:09:03^C
$

Notes:

  • I use the +%-d:%-H:%-M:%-S output format for date (this dashes mean "leave off any leading zero please") because printf seems to interpret digit strings with a leading zero as octal and eventually complains about invalid values.
  • I got rid of the nanoseconds simply because for my purposes I don't need beyond 1-second precision. Therefore I adjusted the sleep duration to be longer to save on compute.

Upvotes: 1

nonopolarity
nonopolarity

Reputation: 151214

Based on a gist by rawaludin:

function stopwatch() {
  local BEGIN=$(date +%s)

  while true; do
    local NOW=$(date +%s)
    local DIFF=$(($NOW - $BEGIN))
    local MINS=$(($DIFF / 60 % 60))
    local SECS=$(($DIFF % 60))
    local HOURS=$(($DIFF / 3600 % 24))
    local DAYS=$(($DIFF / 86400))
    local DAYS_UNIT
    [ "$DAYS" == 1 ] && DAYS_UNIT="Day" || DAYS_UNIT="Days"

    printf "\r  %d %s, %02d:%02d:%02d  " $DAYS $DAYS_UNIT $HOURS $MINS $SECS
    sleep 0.25
  done
}

For people who are not familiar with this: in English, only when it is 1 do we use singular -- Day. When it is 0, 2, 3, 4, 5..., we use plural "Days", so note that it is 0 Days.

Upvotes: 2

Cory Klein
Cory Klein

Reputation: 55870

If you want something simple that includes minutes, seconds, and centiseconds like a traditional stopwatch you could use sw.

sw

Install

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

Usage

# start a stopwatch from 0, save start time in ~/.sw
sw

# resume the last run stopwatch
sw --resume 

Upvotes: 19

lewiatan
lewiatan

Reputation: 1176

For the subtraction you should use bc (An arbitrary precision calculator language).

Here is the example code that fulfill your requirements:

function stopwatch() {
    date1=`date +%s.%N`
    while true; do
        curr_date=`date +%s.%N`
        subtr=`echo "$curr_date - $date1" | bc`
        echo -ne "$subtr\r";
        sleep 0.03
    done;
}

Additional sleep is added to lower the CPU usage (without it on my machine it was almost 15% and with this sleep it lowered to 1%).

Upvotes: 0

Related Questions