Launchd is Apple’s replacement in OS X for several Unix process management utilities, most notably cron. I use it to run several scripts at scheduled times or fixed intervals. Every day my computer is set to download my twitter statuses and check my library card for overdue books. Every morning my computer resets its volume to medium. Every week it backs up the WordPress database of my “family pictures” blog. It syncs my work files between my computer and the university file server.
Almost anything you can do with cron you can do with launchd, but with more power and flexibility. Unlike cron, launchd does not assume that your computer is always running. So if your computer happens to be sleeping at the time a job is scheduled, it will run the job when it wakes up. This is probably the best feature of launchd, because it allows me to run scripts on my iMac while still letting it sleep when I’m not using it.
I have pieced together what I know about using launchd to schedule jobs from tutorials across the internet, trial and error, and the manuals. This is my attempt to gather all my knowledge about this in one place. If there is something you think I should add or fix, let me know.
Because this article is longer than usual.
- Quick start
- Launchd basics
- Tools to manage your agents
- Format of the plist file
- Random thoughts
The best way to get started is to buy Lingon 3 by Peter Borg from the Mac App Store. This will allow you to schedule basic jobs without ever looking at a plist file. Just fill out the fields and you are ready to go:
Even if you have more complicated needs, Lingon gives you a great place to start. Instead of copying and pasting a plist you found on the internet, you can use Lingon to give you something to start with.
Lingon doesn’t have to stay running in the background. It has no menubar icon. It just sets up the job and gets out of the way, letting OS X do the rest. And since OS X already uses launchd to run just about everything, from Spotlight to ssh-agent to the bezel notifications that appear when you change the volume, scheduling jobs will not add any overhead.
Each launchd agent is stored in an xml plist file. The file contains information about what program to run, when to run it, which arguments to use, and other options. Although technically you can make things work no matter where the plist file is saved, it is best to put it in
~/Library/LaunchAgents, because plists in this folder are automatically loaded into launchd when the computer restarts.
Each agent has a label, which must be unique. Apple uses reverse domain syntax
com.apple.whatever, but it doesn’t matter. The plist filename can be anything, but you would be crazy to use anything other than the label, e.g.
com.apple.whatever.plist. Sometimes agents are referred to by the file, and sometimes by the label.
Warning: At some point you will use one of your plists as a template to create another. You will of course give them different filenames, but you will forget to change the label, which means only one of them will work. Hopefully this warning will then enter your mind and you will solve the problem.
Apple uses the terms load and unload to mean that an agent is in the system, ready to go, and start or stop to talk about running or killing the actual process. So all agents in your
LaunchAgents folder are loaded when the computer starts up. Then it pays attention to when they are scheduled and starts them at the appropriate time. If you create a new plist file you need to load it manually. If you change a plist file, you need to unload it and load it again.
Tools to manage your agents
- Launchctl is Apple’s tool, which gives you most control at the expense of complexity.
- Lingon is easy, but has some issues.
- Lunchy is like launchctl, but slightly more convenient.
Apple provides launchctl to manage your agents. The main commands you need are
launchctl load ~/Library/LaunchAgents/org.nathanamy.archive-tweets.plist launchctl unload ~/Library/LaunchAgents/org.nathanamy.archive-tweets.plist launchctl start org.nathanamy.archive-tweets launchctl stop org.nathanamy.archive-tweets launchctl list
unload require the filename, while
stop require the label. The
start command will manually run the job, even if it isn’t the right time. This can be useful for testing. The
stop command just kills the process, but is convenient because you don’t need to know the pid. The
list command shows all loaded agents, with the pid if they are currently running and the exit code returned the last time they ran.
I mentioned Lingon earlier, and it is a great tool for creating an managing launchd agents. It is the easiest way to create a basic plist. When you use Lingon to edit an agent, pressing the Save button automatically loads and unloads the job.
There are some issues with Lingon that you should be aware of. Mainly, Lingon does not support all the possible agent options and is not always careful to preserve changes you make manually. If you manually edit a plist and later edit it using Lingon, you may lose your manual edits without warning.
One other useful tool is Lunchy by Mike Perham. This is a ruby script that speeds up the loading and unloading of agents by allowing you to refer to a plist by any unique substring of the filename. My only issue is that it uses terminology that conflicts with Apple’s. Lunchy uses
start to mean
stop to mean
unload. It mostly compensates by providing very useful commands
restart to unload and then load and
edit to edit the file in your default editor.
Install it using
gem install lunchy and then edit the file using
lunchy edit archive-tweets
Now reload it using
lunchy restart archive-tweets
lunchy allow you to disable an agent using the
-w flag with
stop. I do not recommend this. An agent that has been disabled will not load when the computer restarts and cannot be loaded using
start without using the
-w flag again. You will probably just be confused later about why an agent is not loading even though it is in the right place. Information about which agents have been disabled in this manner is stored in a separate file. In Lion, this is the file
NNN is your user id number (find it using
Format of the plist file
Here is a very basic plist file to run a script every 86,400 seconds.
xml: 1: <?xml version="1.0" encoding="UTF-8"?> 2: <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3: <plist version="1.0"> 4: <dict> 5: <key>Label</key> 6: <string>org.nathanamy.archive-tweets</string> 7: <key>ProgramArguments</key> 8: <array> 9: <string>/Users/grigg/bin/archive-tweets.py</string> 10: </array> 11: <key>StartInterval</key> 12: <integer>86400</integer> 13: </dict> 14: </plist>
There are several other keys you can insert between lines 4 and 13 to activate other options. If you want to see what is available, read the launchd.plist manual.
Note that I have provided the full path to the script, since globs aren’t expanded by default. If you want to expand globs, you can include an
EnableGlobbing key followed by
If your script requires arguments, you would supply these in extra
string tags after line 9.
If you download a script from the internet or write one yourself, make sure it is executable, or this might not work.
By default, anything written to standard out or standard error ends up in the system log. If you would like it to be somewhere else, you can use the
KeepAlive key allows a script to be run multiple times, depending on certain conditions. If you set it to
<true/>, then the script will be run over and over again forever. The following snippet will rerun the script if it returns a nonzero exit code. Read the xml as “Keep the process alive as long as it doesn’t successfully exit”.
xml: <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict>
In most cases, the system will wait 10 seconds between runs of the script to save system resources. You can adjust this with a
ThrottleInterval key, which takes an integer argument, and should be outside the
KeepAlive dictionary. You can also set the agent to stay alive depending on the (non)existence of an internet connection using
NetworkState or of a file using
In older versions of OS X, there was an
OnDemand key which was required. It is now obsolete and has been replaced by
KeepAlive, which is optional. Many of the other examples on the internet still have an
OnDemand key, but you don’t need it.
Your launchd agents are loaded when you log in, but not unloaded when you log out. So the only time your agents aren’t loaded is during the time between a restart and when you log in. If you have multiple users and need something to run no matter who is logged in, you should look into putting it in
If your computer sleeps often, it will be asleep when jobs should run, which means it will run them right when it wakes up, possibly before it connects to the internet. I have experimented with
NetworkState to get a job to repeat itself until there is a network connection. You could also use
SuccessfulExit write the script so that it only returns a nonzero code to mean “run again in 10 seconds.” Either method would have the script running (and presumably failing) every 10 seconds when you have no internet connection. A better idea would be to just sleep 5 seconds at the beginning of your script. Or you could double the run frequency and hope for the best.