# Sleep until specified time

It's sometimes useful to pause until a specified time (but inconvenient to use at or the like), so I wrote a tiny script to wait until a user-specified time point is reached.

I use this when I want to keep the output of preceding and subsequent commands together, but I don't mind blocking the current terminal. Quite often, this would be M-xcompile when I want to wait until after new source code/data have been uploaded, but still have the results in a compilation-mode buffer.

Although it's a very short script, my time at Code Review has taught me that almost any program has something that can be improved, so please make your suggestions!

#!/bin/sh

# Sleep until the specified time

# Accepts any date/time format accepted by 'date'

# Assumes GNU date and GNU sleep

die() {
echo "$@" >&2 exit 1 } usage() { echo "Usage:$0 TIME"
}

test $# = 1 || die$(usage)

case "$1" in --version) echo "sleep_until version 1.0" exit 0 ;; --help) usage exit 0 ;; -*) die "Unrecognised option:$1"
;;
*)
end=$(date -d "$1" +%s.%N)
now=$(date +%s.%N) test${end%.*} -gt ${now%.*} || die "$1 is in the past!"
exec sleep $(echo$end $now - p | dc ) ;; esac  ## Notes: • We need GNU date for its -d option and a good range of input formats. • GNU sleep handles fractional seconds; we could remove .%N from the date formats to work with traditional/POSIX sleep. • I've used dc for the arithmetic so we can use plain sh rather than requiring a more heavyweight shell. ## 2 Answers In a program comment it is said that the program # Accepts any date/time format accepted by 'date'  That information should be printed with the usage help, plus one or two examples. Something like $ ./sleep_until.sh --help
Usage: ./sleep_until.sh TIME

TIME can be any date/time as accepted by date(1).

Examples:

...


That'll save me from looking into the source code (and possibly from looking up man date) in order to figure out the correct parameters.

Due to the truncation in

test ${end%.*} -gt${now%.*} || die "$1 is in the past!"  there is a (albeit small) chance that a valid date is not recognized. This can be avoided by computing the delay first, and then checking it to be positive (e.g. as in How to compare to floating point number in a shell script): end=$(date -d "$1" +%s.%N) now=$(date +%s.%N)
delay=$(echo$end - $now | bc) test 1 -eq$(echo "$delay > 0" | bc) || die "$1 is in the past!"
exec sleep $delay  Note: I'm using bc rather than dc here just because I needed it for the check $delay > 0 and found a bc-based solution first (and then I did not want to use two different tools). Also I am a little bit more familiar with bc. Some criteria are given in the Unix & Linux question How is bc different from dc?, such as “bc is standardised by POSIX and so is probably the more portable of the two.”

The substitution in the final exec line can be simplified. Instead of making a pipeline with echo, we can provide input to dc as a command argument:

        exec sleep $(dc -e "$end $now -p")  I always recommend setting -e and -u options for shell scripts. So add at the beginning: set -eu  This is particularly important, to ensure that we fail neatly when the argument is not understood by date. I think I missed this because I thought that these were Bashisms, but they are actually specified in POSIX. Consider adding a --verbose option to print the sleep duration (in seconds) and target time - this could be valuable with some of the relative times supported by date. The argument parsing and checking would need to be reworked, as the simple $# = 1 check would no longer be adequate.

Also, accept -h as a synonym for --help.

# Improved code

Applying these suggestions and those from the other answer, I have:

#!/bin/sh

# Assumes GNU date and GNU sleep

set -eu

die() {
echo "$@" >&2 exit 1 } usage() { cat <<EOF Usage:$0 [-v|--verbose] TIME
or  $0 --version Sleep until the specified time. TIME can be any date/time as accepted by date(1). Examples:$0 3:14
$0 'next Tuesday' EOF } verbose=false while [ "${1+y}" ]
do
case "$1" in --version) echo "sleep_until version 1.1" exit 0 ;; -h|--help) usage exit 0 ;; -v|--verbose) verbose=true shift ;; -*) die "Unrecognised option:$1"
;;
*)
test \! "${2+y}" || die "Extra arguments after time" end=$(date -d "$1" +%s.%N) now=$(date +%s.%N)
duration=$(dc -e "$end $now -p") case "$duration" in
-*) die "$1 is in the past!";; esac if$verbose
then
printf 'Sleeping for %g seconds until ' $duration date -d "$1"
fi
exec sleep \$duration
;;
esac
done

# If we reach here, we didn't get any non-option argument
die "No time specified"