4

You can think of this like a really simple stopwatch. I'm trying to hack together a bash script that displays the elapsed time since a specified date and updates the output every second.

First, Inside the script you'd specify a UNIX date: Fri Apr 14 14:00:00 EDT 2011. This would be when the stopwatch starts.

Now, when you run the script, you'd see...

06d:10h:37m:01s

and a few seconds later you'd see...

06d:10h:37m:05s

I'm not trying to have a new line printed for every second that elapses. The script should only have 1 line of output, and update it every second. Obviously you could quit the script and start it up again at any time, and it would still be right on since the starting time is hard coded.

Any ideas?

jerzy
  • 2,364
  • 4
  • 20
  • 24
  • Jordanmn, I have tried modifying several scripts I found online by googling "bash stopwatch," "bash elapsed time" and other such queries. – jerzy Apr 19 '12 at 16:35

3 Answers3

5

I use the following code snippet in long-running scripts. The timer runs in a function (separate process) which will be killed (trap) if the main process is terminated by a keybord interrupt. The output shows the time elapsed from the start of the timer:

... working [hh:mm:ss]  00:07:58

The snippet:

#===  FUNCTION  ================================================================
#          NAME:  progressIndicatorTime
#   DESCRIPTION:  Display a progress indicator with seconds passed.
#    PARAMETERS:  [increment]   between 1 and 60 [sec], default is 2 [sec]
#===============================================================================
function progressIndicatorTime ()
{
  declare default=2                                       # default increment [sec]
  declare increment="${1:-$default}"                      # 1. parameter or default
  declare format='\b\b\b\b\b\b\b\b%02d:%02d:%02d'         # time format hh:mm:ss
  declare timepassed=0
  declare seconds minutes hours

  [[ ! "$increment" =~ ^([1-9]|[1-5][0-9]|60)$ ]] && increment=$default
  printf " ... working [hh:mm:ss]  00:00:00"
  while : ; do                                            # infinite loop 
    ((seconds=timepassed%60))
    ((minutes=timepassed/60))
    ((hours=minutes/60))
    ((minutes=minutes%60))
    printf "$format" $hours $minutes $seconds
    sleep $increment || break                             # sleep ...
    ((timepassed+=increment))
  done
}    # ----------  end of function progressIndicatorTime  ----------

progressIndicatorTime &                                   # run progress indicator
declare progressIndicatorPid=${!}                         # save process ID
trap  "kill $progressIndicatorPid" INT TERM               # trap keyboard interrupt

#
# run long command
#

kill -s SIGTERM $progressIndicatorPid                     # terminate progress indicator
Fritz G. Mehner
  • 16,550
  • 2
  • 34
  • 41
4

The art could use some work, but give this a try:

#!/bin/bash

ref_date='Thu Apr 19 17:07:39 CDT 2012'
ref_sec=$(date -j -f '%a %b %d %T %Z %Y' "${ref_date}" +%s)
update_inc=1

tput clear
cat <<'EOF'


            [|]     [|]
         _-'''''''''''''-_
        /                 \
       |                   |
       |                   |
       |                   |
        \                 /
         '-_____________-'
EOF

while :
do
  ((sec=$(date +%s) - ${ref_sec}))
  ((day=sec/86400))
  ((sec-=day*86400))
  ((hour=sec/3600))
  ((sec-=hour*3600))
  ((min=sec/60))
  ((sec-=min*60))
  tput cup 6 14
  printf "%.2id:%.2ih:%.2im:%.2is\r" ${day} ${hour} ${min} ${sec}
  sleep ${update_inc}
done

exit 0

Note that the syntax of the first date command is for OSX.

For GNU date, use date --date="${ref_date}" +%s

Nick Atoms
  • 572
  • 2
  • 5
1

If you have Bash4 or ksh93, you can use the printf %()T syntax to print strftime formats. The following example is Bash 4 printing in the format you want. Bash precision is limited to 1 second. ksh93 supports floats and would require some modification.

#!/usr/bin/env bash

# To supply a default date/time. To use now as the default, leave empty, or run with a null first arg.
deftime='Fri Apr 14 14:00:00 EDT 2011'

if (( ${BASH_VERSINFO[0]}${BASH_VERSINFO[1]} >= 42 )); then
    unixTime() {
        printf ${2+-v "$2"} '%(%s)T' -1
    }
else
    unixTime() {
        printf ${2+-v "$2"} "$(date '+%s')"
    }
fi

stopWatch() {
    local timestamp="$(date ${1:+'-d' "$1"} '+%s')" curtime day=$((60*60*24)) hour=$((60**2))
    # unixTime -1 timestamp

    while
        unixTime -1 curtime
        (( curtime -= timestamp ))
        printf '%02dd:%02dh:%02dm:%02ds\r' $(( curtime / day )) $(( (curtime / hour) % 24 )) $(( (curtime / 60) % 60 )) $(( curtime % 60 ))
        do sleep 1
    done
}

stopWatch "${1-deftime}"

You may also be interested in the $SECONDS variable. Unfortunately there is no convienient way to accept a human-readable date like you want. It would require considerable parsing effort. The date command (particularly GNU date) can do that to a limited extent.

ormaaj
  • 6,201
  • 29
  • 34
  • I should have specified that I am using GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0) on OS X Snow Leopard. – jerzy Apr 19 '12 at 16:32
  • I am getting an error that says ./timer.sh: line 4: printf: `(': invalid format character. Do you have an idea of how to fix this bug? – jerzy Apr 19 '12 at 16:33
  • The feature I'm using here requires Bash 4.2. You'd have to replace all the `printfs` with date calls. I Think that's as easy as changing that unixTime function... I'll have to test it – ormaaj Apr 19 '12 at 16:59
  • Alright now should work in Bashes 3.1 and newer. The only advantage to having 4.2 here is saving a subshell. – ormaaj Apr 19 '12 at 18:16
  • Wow!! Very Cool!! But are you saying it can't have a hard-wired start date? The way it is now if I close the shell it starts all over again. – jerzy Apr 19 '12 at 19:00
  • You could give it a hardcoded start date if you wanted. You currently call the script as: `scriptname "date"` where date is a valid argument to the date `-d` option. Currently this is made so that if no args are given, then the current time is used. If you want the script to not stop then run it in the background. If you wanted the date to persist between invocations then you would have to have it write the timestamp to a file and reference that. – ormaaj Apr 19 '12 at 19:07