As comedians and nymphomaniacs will tell you, timing is everything. So, in this installment of my popular
bash (retro) programming series, we will build a
bash script called
sleeper for suspending and waking up a (
Linux) computer at a predetermined time. We will then invoke it from
crontab for automated repeated suspensions and awakenings. Some applications:
“Saving the planet and your computer” i.e., suspending machines when they are not being used for some time. Suppose that you want to run some regular backup and maintenance at 3am, though? This script will awaken the machine just in time to run the maintenance cron job and then put it back to sleep.
Regular downloads/uploads of data from the cloud/internet/sensor.
Regular generation and publishing of (static) websites.
Dedicated hardware that only does one job at regular intervals. For example, time-lapse photography by a computer controlled camera. Perhaps a
Raspberry Pi running in a remote location on batteries? This will definitely make the batteries last a lot longer.
My application (Automated Web Trader) that motivated me to write
sleeper ticks boxes 1.-3.
There are some existing facilities for machines self-suspending after some period of inactivity but they are somewhat unreliable in practice. That is why admins prefer to err on the side of caution and leave everything on all the time. Clearly, the superior solution is to wake up and to suspend again at exactly the required times under explicit own control.
Disclaimer: No warranties, implied or otherwise, are given. No responsibilities are accepted. This article is for educational purposes only. If you lock your computer into a state of permanent suspension, you will have only yourself to blame.
rtcwake has various modes (-m) of saving the state of the machine. The most useful ones are mem and disk,
-m mem being the faster one. However, for longer suspensions and more energy saving, consider
-m disk. Other main options of interest are -t (wake up at a specified time) or
-s (wake up in specified number of seconds). We invoke it within
sleeper script in this way:
sudo rtcwake -m mem -u -s $(( $G-($(date +%s)-$S)%$G-$A ))
rtcwake can only be invoked with sudo privileges, as it affects the whole machine. It should be a single user computer, or its users must be warned in advance of the pending suspension. Sudo has repercussions for invoking it from cron. It is inadvisable, for security reasons, to call it from other existing user crontabs, as they would all have to be run under
root. It is better to invoke
rtcwake from its own dedicated
root crontab and just synchronise its timings with the user’s normal tasks crontabs.
-u means use UTC clock. This avoids many potential headaches with timezones an Summer times and whatnot. UTC achieves safer synchronisation with
crontab, which should also run on UTC clock by setting environment variable TIMEZONE=UTC as the first line in the crontab.
the rest of the line specifies the number of seconds to be spent in the suspended state before re awakening. The calculation will be explained presently in the next section. In a recurring scenario this is simpler than using
-t specific-time, as the specific times will be changing all the time.
The time resolution of
rtcwake is seconds, whereas the smallest time unit of
crontab is a minute. Accordingly,
sleeper will work in seconds, whereas
crontab in minutes. We need to bear this in mind when synchronising between them.
The actual usage we want is the inverse of the way that
rtcwake works. We want
sleeper to be waking-centric, rather than sleep-centric. That is to say, the logical sequence of events is to be: wake up, do some communications and calculations tasks, then go back to sleep.
There is a potential problem with this. Due to lag etc, those communications will take somewhat unpredictable amounts of time. So, it is only when they are actually finished that we can calculate exactly how many seconds remain till the next regular waking time.
The beauty of this formula lies in its use of modular arithmetic to solve this problem:
$(( $G-($(date +%s)-$S)%$G-$A ))
bashvariables in this expression,
$G,$S,$A, are supplied to
sleeperas option values. Any that are left unspecified are assigned default values.
$Sis the start offset for the first cron invocation in any hour, in seconds.
$Gis the gap between subsequent regular cron invocations
($S < $G).
$(date +%s)returns the
unix time(continuous count of seconds since 1.1.1970 till present).
$Sfrom the unix seconds to get the time it would be be now if the start offset delay was zero.
%$Gfinds the remainder modulo
$G, which is precisely the time spent on tasks up to this moment. Subtracting this from $G gives the time to run till the next scheduled tasks crontab invocation. In other words, adding the result to the present time, which is what
rtcwakedoes internally, takes it exactly to the next regular $S+n*$G waking up milestone.
$A(advance), shortening the suspension by a few seconds to give the system time to wake up in time.
Now we are at the stage where we can invoke
sudo sleeper manually at any time. It will suspend the computer immediately and wake up again
$A seconds before the next scheduled crontab invocation of some regular tasks.
Ideally, we would want to call
sudo sleeper at the end of our main recurring tasks crontab script. However, because of the difficulties with permissions and security discussed above, we will make it build its own
root crontab to run alongside the main user one. We add option -r for this purpose:
sudo sleeper -r needs to be invoked only once, at the beginning. Thereafter,
sleeper will be periodically reinvoked from its own crontab. To cancel and regain permanently wakeful state, use
sudo sleeper -w (have to act quickly, while still awake).
One subtle problem with this is that
sleeper can only be invoked by
crontab at whole minutes, so when the usual tasks are finished, we have to wait a little (and our precise calculation of the remaining sleep time above is no longer strictly necessary). My main task, written in super fast Rust, usually finishes in a few seconds and the computer then has to idle till one whole minute is up. In practice, this is not much of a problem and it does give us time to issue
sudo sleeper -w when needed. Should your applications need more than one minute to run, then it will be necessary to add the relevant number of “waking” minutes, rounded up. Here is the -r part of the script:
# set up and start crontab which will suspend one minute after the regular $S+$G time # and reawaken $A seconds before the next one, using this script (this time without -r) if [ $R ]; then SM=$(( $S/60+1 )) # start in minutes, plus one, to allow one awake minute GM=$(( $G/60 )) # gaps in minutes printf "TZ=UTC\n$SM-59/$GM * * * * sudo $PD/bin/sleeper -a $A -s $S -g $G"\ " >> $PD/data/sleepercronlog\n" | crontab -u root - fi
Note how the
$GM parameters (
$M translated into minutes) map directly into the first entry (for minutes) in the
$SM-59/$GM. Similar thing can be done for hours and days in the following fields, should longer intervals be needed.
Cron is activated by piping the two lines crontab as standard input (-) into
crontab -u root -. Meaning that the user (owner) of this crontab will be
I had permission problems until I have put
sudo also in front of the invocations of
sleeper. This fussiness took me by surprise, considering that the script and the crontab were already invoked with
The full source is here: sleeper