When I tried to schedule a job for the first time in macOS, I faced the old news that Apple deprecated the good old
cron in favor of
launchd and a family of utilities to launch and debug jobs.
While I believe
cron is going to outlive us, I decided to use
launchd because it allows running jobs after the computer wakes up from sleep.
Here are the notes I took during the process:
In the launchd lexicon, a ‘daemon’ is, by definition, a system-wide service of which there is one instance for all clients. An ‘agent’ is a service that runs on a per-user basis. Daemons should not attempt to display UI or interact directly with a user’s login session. Any and all work that involves interacting with a user should be done through agents.
To describe jobs, you use ‘Property List Files’ (
.plist files from now on.)
.plist files use XML syntax to describe a job, and depending on where are located they fulfill a different purpose:
|~/Library/LaunchAgents||Per-user agents provided by the user.|
|/Library/LaunchAgents||Per-user agents provided by the administrator.|
|/Library/LaunchDaemons||System-wide daemons provided by the administrator.|
|/System/Library/LaunchAgents||Per-user agents provided by Apple.|
|/System/Library/LaunchDaemons||System-wide daemons provided by Apple.|
After the system boots and the kernel is running,
launchd is run to finish the system initialization. As part of that initialization, it goes through the following steps:
SIGTERMsignal to all the daemons.
In its most basic form a
plist file only requires a few keys defined:
Label: a unique identifier.
ProgramArguments: arguments to launch the daemon.
KeepAlive: specifies whether the daemon launches on-demand or must always be running.
<?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.example.copyfiles</string> <key>ProgramArguments</key> <array> <string>cp</string> <string>dir1</string> <string>dir2</string> </array> <key>KeepAlive</key> <true/> </dict> </plist>
A description of all valid keys is located in the launchd.plist man page.
Notable keys are
StartCalendarInterval, that allows you to define time intervals to run the process.
<key>StartCalendarInterval</key> <dict> <key>Minute</key> <integer>45</integer> <key>Hour</key> <integer>13</integer> <key>Day</key> <integer>7</integer> </dict>
One of the first things you want to do is enable logging to know what is going on in your process.
To do this you need to set
true and provide paths for standard output and error output:
<key>StandardOutPath</key> <string>/var/log/myjob.log</string> <key>StandardErrorPath</key> <string>/var/log/myjob.log</string> <key>Debug</key> <true/>
tip: the files must be in a directory with writing access.
Once a job is in the right folder, you have to either restart your computer to make it load, or instruct
launchctl to load the process with:
$ launchctl load ~/Library/LaunchAgents/com.myprocess.plist
If you make changes to your
.pid file, you’ll want to reload the script in a two-step process:
$ launchctl unload ~/Library/LaunchAgents/com.myprocess.plist $ launchctl load ~/Library/LaunchAgents/com.myprocess.plist
This is a convenient alternative to editing the
launchd.plist for the service and then reloading. To use
launchctl to trigger a debug process:
$ sudo launchctl debug gui/$UID/com.myprocess.plist --stdout --stderr
$UIDis your user ID, it’s a variable automatically set for you.
launchctl is listening, you need to open another terminal tab and start your process as described below. Everything is logged in the listening tab.
launchctl debugallows you to do many more than this, check out the man page.
To kick-start a process at any time:
$ launchctl start com.myprocess.plist
I try to post once a week interesting stuff about programming, *nix, and the web. If you’d like to be notified when a new post goes out, you can subscribe with the form below.