We spend a lot of time doing tedious things with computers. Magical though they may be, and as powerful as they are, folks still use computers to manually enable repetitive tasks. With just a teaspoon of programming knowledge, you can automate the boring parts of your computing life.
launchd is one of those tools that can help you out. It’s a command-line tool for starting, stopping and managing scripts and processes. Linux users or system admins will recognize
cron in macOS.
What are daemons?
Daemons (pronounced “demons”) are processes that run in the background of your computer. If you’re interested in a little etymology, the unusual name comes from Maxwell’s demon, an imaginary agent that sorts molecules in a thermodynamics thought experiment. Their process names typically end in “d,” and you’ll find them everywhere. Take a look at Activity Monitor and you’ll see plenty daemons spawned by macOS itself, with names like trustd, fseventsd, powerd and even lsd. Unlike applications, daemons run as processes, and are not under the direct control of the user or another application. On macOS, system and user daemons alike are under the control of the launchd framework, which decides when they start and stop.
In order to run daemons through launchd, you’ll need to write some scripts. Scripts represent the tasks that the computer will run, and you’ll need to do a little programming. The most common scripting language is bash, but you can also use Python. It’s a fairly straightforward scripting language that you can pick up from some excellent online tutorials. Here’s a (very Web 1.0) bash scripting how-to and a Lifehacker post on bash scripts that are a good starting point.
Scripts in launchd are trigged by job definitions. These are preference list (.plist) files stored in specific directories. These XML files give the job a name, specify the script that should be launched and indicate when the script should fire. Once you’ve written your script, you’ll write and load a job definition that launches the script at the appropriate time.
A job definition looks something like what’s below:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>local.restart</string> <key>Program</key> <string>/Users/user/Scripts/restart.sh</string> <key>RunAtLoad</key> <true/> </dict>
Tweak this files as needed with the parameters below. Then save the file with a .plist extension in the correct directory (see below).
There are a few key parts to the job description:
- Label: the name of the job within launchd. Must be unique for each job. These are traditionally written in reverse domain notation, (com.apple.screensaver, for example). “local” is a great domain for private agents.
- Program: the fully-qualified path to the script launched by this job description
- RunAtLoad: describes when the script should be run. There are a few different options here:
- RunAtLoad: run as soon as the job definition is loaded. Runs only once per load unless otherwise scheduled. False if unspecified.
- StartInterval: start the job every n seconds. The example below will run the job every 7200 seconds, or every 2 hours. Time must be specified in seconds.
- StartCalendarInterval: run the job at a specific time and date. The below will run the job every day at 9 AM.
<key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>9</integer> <key>Minute</key> <integer>0</integer> </dict>
Agents vs. daemons
Once you’ve written your job description, you’ll need to save it in the appropriate directory.
launchd further distinguishes between agents and daemons. An agent runs on behalf of the logged in user, while a daemon runs under the root user. This means that, if you want to restrict a script to a specific user account, you can use an agent. If you want something to run no matter who is logged in, you’ll use a daemon.
The difference between agents and daemons is drawn from where they’re saved on the computer:
- ~/Library/LaunchAgents runs on behalf of the logged in user
- /Library/LaunchDaemons runs on behalf of the root users.
You’ll need to save your plist in the correct location in order to make the script run properly. If you’re not sure where to save it, ~/Library/LaunchAgents is probably a good choice.
Loading jobs into launchctl
Once you’ve created you scripts and saved your job description into the right place, you’ll need to load it into
launchctl. Provided you saved your job description in one of the previously mentioned directories, this will happen automatically on future logins.
To see what’s current running in laucnhctl, you can use
launchctl list. This giant list can be grepped for your script by label with something like the following:
launchctl list | grep local.restart
To load a script, open up Terminal and use the following command. Replace the directory path with the path to your script. Note that the job description’s filename and the job name are almost identical, which is good practice for organizing your scripts.
launchctl load ~/Library/LaunchAgents/local.restart.plist
To remove the script from the
launchctl queue, use the
launchctl unload ~/Library/LaunchAgents/local.restart.plist
Loading a job puts it into the launchd queue, and the job will run at the time specified in its launch conditions. If you want to run an script immediately no matter what, you should use the “start” command:
launchctl start local.restart
This command takes the job’s label, and will only work if the job has already been loaded into
You can use launchd to create scripts that do things like clean up files, restart your server on a schedule or run an application when a certain file is appears. To learn a lot more about launchd, you can check out the laucnhd tutorial, from which much of the this information is drawn.
You might also be interested in the following: