Sometime Friday evening, I found myself with a malfunctioning alarm clock cellphone that kept turning off at the wrong times. I had to get up early the next morning to drive a friend to the airport, so I decided my laptop would just have to shoulder the burden.

I’ve tried a couple different OS X alarm clock apps in the past, but even the simplest of them seemed like overkill for the task at hand:

At a predetermined time, do something obnoxious enough to wake me up.

A simple Bash script seemed like the obvious answer, but how to play a sound from bash? Opening a file in iTunes is easy, but the whole idea was to cut down on bloat. A little Googling pointed me towards a utility called afplay, and I was on my way.

With a total investment of about 10 minutes, I had this uncommented, somewhat unreadable, but working script:

#!/bin/bash

t="04:30"
t_h=`echo $t | awk -F: '{print $1}'`
t_m=`echo $t | awk -F: '{print $2}'`
t_s=`dc -e "$t_h 60 60 ** $t_m 60 *+p"`

c=`date | awk '{print $4}'`
c_h=`echo $c | awk -F: '{print $1}'`
c_m=`echo $c | awk -F: '{print $2}'`
c_s=`echo $c | awk -F: '{print $3}'`
c_t=`dc -e "$c_h 60 60 ** $c_m 60 * $c_s ++p"`

til=`dc -e "$t_s $c_t -p"`

sleep $til

while :
do
afplay /System/Library/Sounds/Hero.aiff
done

It woke me up, all right, but the rapid playing of that system sound and having to fumble for Ctrl-c to stop the thing were pretty annoying. Later, having gone to the airport and then back to sleep for a few hours, I polished it up a bit:

#!/bin/bash

# A simple alarm clock script

echo "What time should the alarm go off? (HH:MM)"
read target

# sleep interval is 15 minutes
snooze=`dc -e "15 p"`

# convert wakeup time to seconds
target_h=`echo $target | awk -F: '{print $1}'`
target_m=`echo $target | awk -F: '{print $2}'`
target_s_t=`dc -e "$target_h 60 60 ** $target_m 60 *+p"`

# get current time and convert to seconds
clock=`date | awk '{print $4}'`
clock_h=`echo $clock | awk -F: '{print $1}'`
clock_m=`echo $clock | awk -F: '{print $2}'`
clock_s=`echo $clock | awk -F: '{print $3}'`
clock_s_t=`dc -e "$clock_h 60 60 ** $clock_m 60 * $clock_s ++p"`

# calculate difference in times, add number of sec. in day and mod by same
sec_until=`dc -e "24 60 60 **d $target_s_t $clock_s_t -+r%p"`

echo "The alarm will go off at $target."

sleep $sec_until

# snooze loop
while :
do
  echo -e "\nWake up!"
  ./wakeup_playlist.sh &
  bpid=$!
  disown $bpid                          # eliminates termination message
  read -n1 input
  for bsub in $(ps -o pid,ppid -ax | \
                awk "{ if (\$2 == $bpid) { print \$1 }}")
  do
    kill $bsub                          # kill children
  done
  kill $bpid
  if [ "$input" == "Q" ]
  then
    echo -e "\nGood morning!"
    exit
  else
    echo -e "\nSnoozing for $snooze seconds..."
    sleep $snooze
  fi
done

You’ll notice I used dc wherever basic arithmetic was required. It’s an RPN or postfix calculator, so operands are pushed onto the stack, and operators pop them off and then push the result. This was an arbitrary choice; using dc over bc or even awk for basic math has everything to do with my preference for postfix math. (Lots of nostalgia for using my dad’s HP-15C calculator to do math homework as a kid.)

The “buzzer” code has been exported to another script, for the sake of the snooze loop and because it’s use of afplay is specific to Mac OS X 10.5 and later. By running it as a background process, the main script can wait for user input. Any input character other than Q kills the buzzer process and sleeps for the set interval, while on Q the script exits cleanly. The kill command causes a termination message on stderr from the killed process, so the buzzer process gets disowned to suppress that message.

You can also find the above code on GitHub, including a couple buzzer scripts that will only run on Mac OS X.