I was making dinner and needed to time something. My phone already had another timer going, and running two timers simultaneously is clearly beyond the capacity of a modern smartphone, so I reached for my laptop instead. And as I Googled “macos desktop timer” (because having any timer at all built into macOS is clearly an extravagance, and not one Apple has seen fit to bequeath us with), my Wi-Fi dropped. So I grabbed a terminal, and ran:
$ sleep $((15 * 60)) && say "Onion time"
(For those unfamiliar with macOS, the say
utility is a simple frontend for the built-in text-to-speech engine. On Linux, substitute something like ffplay ding.wav
– I like a microwave bell.)
This seemed convenient, so after the culinary crisis had passed, I wrapped the snippet in a function and added it to my .profile
.
But then I thought it would be nice to have a real-time progress indicator. I thought about writing my own with a loop, a one-second sleep, and a printf
involving a carriage return to repeatedly re-render a counter on the same line. Something like:
remaining="$duration" while [ $remaining -gt 0 ] do printf "\r% 10d" "$remaining" remaining=$(($remaining - 1)) sleep 1 done echo
But that leads to wanting to display both elapsed and remaining times, and maybe ETA, and really, a nice progress bar is what I really want. So, what do I already have installed which can give me a progress bar? Well, pv(1) is pretty great. So all I need to do is feed it a byte every second for as long as I want my timer to run. Except, not quite, because I remembered that pv can enforce rudimentary rate-limiting. So maybe I just need to feed it as many bytes as the number of seconds I want the timer to run for, and limit the throughput to 1 B/s. Then select a set of display options which are only concerned with time (not sizes or throughput).
All said and done, I ended up with:
alarm () { if [ $# -lt 1 ] then echo "Usage: alarm duration [message]" 1>&2 return 1 fi duration="$1" shift message="$@" if [ -z "$message" ] then message="This is your $duration second alarm." fi dd if=/dev/zero bs="$duration" count=1 2>/dev/null \ | pv --progress --timer --eta --size "$duration" --rate-limit 1 >/dev/null \ && say --voice Samantha "$message" }
In action, it looks like this:
0:00:03 [=========> ] 30% ETA 0:00:07
And I’m sure I’ll continue to be pretty happy with it until I get something very wet or very powdery all over my laptop.