Time condition loop in shell
Caveat: All solutions in this answer - except the ksh
one - can return up to (but not including) 1 second early, since they're based on an integral-seconds counter that advances based on the real-time (system) clock rather than based on when code execution started.
bash
, ksh
, zsh
solution, using special shell variable $SECONDS
:
Slightly simplified version of @bsravanin's answer.
Loosely speaking, $SECONDS
contains the number of seconds elapsed so far in a script.
In bash
and zsh
you get integral seconds advancing by the pulse of the system (real-time) clock - i.e., counting behind the scenes does not truly start at 0
(!), but at whatever fraction since the last full time-of-day second the script happened to be started at or the SECONDS
variable was reset.
By contrast, ksh
operates as one would expect: counting truly starts at 0
when you reset $SECONDS
; furthermore, $SECONDS
reports fractional seconds in ksh
.
Therefore, the only shell in which this solution works reasonably predictably and precisely is ksh
. That said, for rough measurements and timeouts it may still be usable in bash
and zsh
.
Note: The following uses a bash
shebang line; simply substituting ksh
or zsh
for bash
will make the script run with these shells, too.
#!/usr/bin/env bash
secs=3600 # Set interval (duration) in seconds.
SECONDS=0 # Reset $SECONDS; counting of seconds will (re)start from 0(-ish).
while (( SECONDS < secs )); do # Loop until interval has elapsed.
# ...
done
Solution for POSIX-features-only shells, such as sh
(dash
) on Ubuntu ($SECONDS
is not POSIX-compliant)
Cleaned-up version of @dcpomero's answer.
Uses epoch time returned by date +%s
(seconds elapsed since 1 January 1970) and POSIX syntax for the conditional.
Caveat: date +%s
itself (specifically, the %s
format) is not POSIX-compliant, but it'll work on (at least) Linux, FreeBSD, and OSX.
#!/bin/sh
secs=3600 # Set interval (duration) in seconds.
endTime=$(( $(date +%s) + secs )) # Calculate end time.
while [ $(date +%s) -lt $endTime ]; do # Loop until interval has elapsed.
# ...
done
You can use the loop
command, available here, like so:
$ loop './do_thing.sh' --for-duration 1h --every 5s
Which will do the your thing every five seconds for one hour.
You can try this
starttime = `date +%s`
while [ $(( $(date +%s) - 3600 )) -lt $starttime ]; do
done
where 'date +%s'
gives the current time in seconds.
The best way to do this is using the $SECONDS variable, which has a count of the time that the script (or shell) has been running for. The below sample shows how to run a while loop for 3 seconds.
#! /bin/bash
end=$((SECONDS+3))
while [ $SECONDS -lt $end ]; do
# Do what you want.
:
done