How to use Launchd

Mac crontab command seems to be deprecated on Mac OS X, and the Apple documentation encourages you to use their “Mac launchd” facility.

launchd

launchd manages processes, both for the system as a whole and for individual users. The primary and preferred interface to launchd is via the launchctl(1) tool which (among other options) allows the user or administrator to load and unload jobs. Where possible, it is preferable for jobs to launch on demand based on criteria specified in their respective configuration files.

There are three main directories you can use with launchd:

  • /Library/LaunchDaemons Put your plist scripts in this folder if your job needs to run even when no users are logged in.

  • /Library/LaunchAgents Put your plist scripts in this folder if the job is only useful when users are logged in. (Note: I learned that this has the side-effect of your job being run as ‘root’ after a system reboot.)

  • ~/Library/LaunchAgents Put your plist files in this folder if the job is only useful when users are logged in. (When your plist configuration file is placed here, your job will be run under your username.)

Note that when you use the first two directories shown here, you must use the “sudo” command to edit your files.

launchd.plist

launchd.plist – System wide and per-user daemon/agent configuration files. Daemons or agents managed by launchd are expected to behave certain ways.

XML PROPERTY LIST KEYS

The following keys can be used to describe the configuration details of your daemon or agent. Property lists are Apple’s standard configuration file format. Please see plist(5) for more information. Please note: property list files are expected to have their name end in “.plist”. Also please note that it is the expected convention for launchd property list files to be named <Label>.plist. Thus, if your job label is “com.apple.sshd”, your plist file should be named “com.apple.sshd.plist”.

Label <string>: This required key uniquely identifies the job to launchd.

UserName <string>: This optional key specifies the user to run the job as. This key is only applicable when launchd is running as root.

StartCalendarInterval <dictionary of integers or array of dictionary of integers>

This optional key causes the job to be started every calendar interval as specified. Missing arguments are considered to be wildcard. The semantics are much like crontab(5). Unlike cron which skips job invocations when the computer is asleep, launchd will start the job the next time the computer wakes up.

  • Minute <integer> The minute on which this job will be run.
  • Hour <integer> The hour on which this job will be run.
  • Day <integer> The day on which this job will be run.
  • Weekday <integer> The weekday on which this job will be run (0 and 7 are Sunday).
  • Month <integer> The month on which this job will be run.

StandardInPath <string> This optional key specifies what file should be used for data being supplied to stdin when using stdio(3).

similar: standardOutPath <string> and StandardErrorPath <string>

The following XML Property List simply keeps “exampled” running continuously:

<xml version="1.0" encoding="UTF-8">
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.example.exampled</string>
        <key>ProgramArguments</key>
        <array>
                <string>exampled</string>
        </array>
        <key>KeepAlive</key>
        <true/>
</dict>
</plist>

launchctl

launchctl interfaces with launchd to load, unload daemons/agents and generally control launchd. launchctl supports taking subcommands on the command line, interactively or even redirected from standard input. These commands can be stored in $HOME/.launchd.conf or /etc/launchd.conf to be read at the time launchd starts.

SYNOPSIS: launchctl [subcommand [arguments …]]

SUBCOMMANDS

  • load: Load the specified configuration files or directories of configuration files. Jobs that are not on-demand will be started as soon as possible. Note that per-user configuration files (LaunchAgents</code>) must be owned by the user loading them. All system-wide daemons (LaunchDaemons) must be owned by root.

  • unload: Unload the specified configuration files or directories of configuration files. This will also stop the job if it is running.

  • submit: A simple way of submitting a program to run without a configuration file. This mechanism also tells launchd to keep the program alive in the event of failure.

  • list: With no arguments, list all of the jobs loaded into launchd in three columns.

Example

For my purposes, I want to run a python script every 10 minute to login v2ex.

$ cd ~/Library/LaunchAgents
$ mvim com.xuelang.crontab_v2ex.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.xuelang.crontab_v2ex</string>

        <key>ProgramArguments</key>
        <array>
                <string>/Users/xuelang/python/learn_modules/beautifulsoup/v2ex_auto.py</string>
        </array>

        <key>StartInterval</key>
        <integer>60</integer>

        <key>RunAtLoad</key>
        <true/>

        <key>StandardErrorPath</key>
        <string>/Users/xuelang/schedule_err.log</string>

        <key>StandardOutPath</key>
        <string>/Users/xuelang/schedule.log</string>
</dict>
</plist>

This Mac plist file can be read as “Run the script /Users/xuelang/python/learn_modules/beautifulsoup/v2ex_auto.py every 5 minutes, and redirect the standard output and standard error as shown”. Remember that the path must be absolute and can not inculde ~.

Next, it’s important to know that your Mac OS X system won’t pick up on this change immediately. You have to tell the Mac launchd daemon to load it, using the Mac launchctl command, like this:

$ cd ~/Library/LaunchAgents
$ launchctl load com.xuelang.crontab_v2ex.plist
$ mvim com.xuelang.crontab_v2ex.plist
$ launchctl list | grep xuelang
-    0    com.xuelang.crontab_v2ex
1377    -    com.google.Chrome.framework.service_process/Users/xuelang/Library/Application_Support/Google/Chrome

After issuing the “launchctl load” command I started getting output from the script

$ cat ~/schedule.log
Your money:  22  45
You haved got the award.
Your money:  22  45
You haved got the award.
...

$ cat ~/schedule_err.log
Traceback (most recent call last):
  File "/Users/xuelang/python/learn_modules/beautifulsoup/v2ex_auto.py", line 86, in <module>
    get_award(usrname, passwd)
  File "/Users/xuelang/python/learn_modules/beautifulsoup/v2ex_auto.py", line 44, in get_award
    headers=post_headers,

unload the task.

$ cd ~/Library/LaunchAgents
$ launchctl unload com.xuelang.crontab_v2ex.plist
$ launchctl list | grep xuelang
1377    -    com.google.Chrome.framework.service_process/Users/xuelang/Library/Application_Support/Google/Chrome

CalendarInterval Example

This StartCalendarInterval example should mean “run at 3:55 am” every day:

<key>StartCalendarInterval</key>
<dict>
        <key>Hour</key>
        <integer>3</integer>
        <key>Minute</key>
        <integer>35</integer>
</dict>

Another one:

<key>StartCalendarInterval</key>
<array>
        <dict>
                <key>>Day</key>
                <integer>22</integer>
                <key>Hour</key>
                <integer>12</integer>
                <key>Minute</key>
                <integer>0</integer>
        </dict>
        <dict>
                <key>Day</key>
                <integer>8</integer>
                <key>Hour</key>
                <integer>12</integer>
                <key>Minute</key>
                <integer>0</integer>
        </dict>
</array>

References:
Mac crontab - Mac OS X startup jobs with crontab, er, launchd
Mac OS X launchd examples
launchd plist StartInterval and StartCalendarInterval