Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top