If you enjoy automating things and work on a macOS system, launchd
is a tool you must know! But what is it, and how does it work?
launchd
is a process on macOS that manages the execution and scheduling of background processes (daemons). It replaces older time-based job schedulers for Unix systems such as cron
. In short, it is necessary if you want to schedule the execution of your scripts at specific times or intervals.
I came across launchd
when I wanted to automate downloading and deleting data from a Google Sheets file to which a microcontroller saves temperature and humidity measurements. Over time, the document would fill up, and I had to manually download and delete the data to make space for more sensor readings. Therefore, I wrote an R script that takes care of this for me. In order to automate the execution of this script, I needed launchd
.
Getting Started
It is pretty simple to use launchd
if you know how. To set it up, you’ll need to create a LaunchAgent property list file (.plist
) in XML format. This file describes the process or program you want to launch, its arguments, and when and how often to execute it.
In this short tutorial, we will create a simple .plist
file that executes a script at a certain time during the day. There are a lot more customisation options and things you can do with launchd
that I won’t go into. If you are curious, you can read up on them using the following terminal command.
man launchd.plist
Creating a .plist file
The overall structure of a .plist
file is always identical. Below you can find the file that I have created for my specific task, which I called com.jpq.download_and_upload_data.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.jpq.download_and_upload_data</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/Rscript</string>
<string>/Users/user/Documents/sensor_data/download_and_upload_data.R</string>
<array>
</key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>13</integer>
<key>Minute</key>
<integer>0</integer>
<dict>
</dict>
</plist> </
You can see that there are three main <key>
fields in the file that specify Label
, ProgramArguments
and StartCalendarInterval
.
<key> |
Description |
---|---|
Label |
Uniquely identifies the job. Usually the file name. It has the structure com.creator.FileName . |
ProgramArguments |
An array of strings which contain the tokenised arguments and the program to run. |
StartCalendarInterval |
Contains a dictionary with <key> /integer fields that specify the time that the script should be executed at. |
ProgramArguments
ProgramArguments
specify what should be executed how. You would specify the execution of the script in the same way that you would also execute it in the terminal, with the exception that you would tokenise (split up) the prompt.
In our .plist
file above, I use the Rscript
interpreter that is usually located at /usr/local/bin/Rscript
to execute my R script located at /Users/user/Documents/sensor_data/download_and_upload_data.R
If you have a shell script .sh
file that you want to execute directly, you can use Program
instead of ProgramArguments
and provide the path to the script directly.
key>Program</key>
<string>/Users/user/Scripts/script.sh</string> <
If you want to run a shell script for the first time, make sure that you have permission by using chmod
in the terminal.
chmod u+x script.sh
StartCalendarInterval
The StartCalendarInterval
can set a specific time point at which the script should be executed. The below example executes the script on the 15th of July at 13:30 if this day is a Sunday.
key>StartCalendarInterval</key>
<dict>
<key>Day</key>
<integer>15</integer>
<key>Hour</key>
<integer>13</integer>
<key>Minute</key>
<integer>30</integer>
<key>Month</key>
<integer>7</integer>
<key>Weekday</key>
<integer>0</integer>
<dict> </
If you simply leave out some of these keys from the dictionary, they are treated as a wildcard, meaning they are not considered for the execution of the job.
One important advantage of using launchd
over cron
is that if a job cannot be executed at its designated time, it will instead be executed at the next possible time. In addition, if a job cannot be run at multiple scheduled times, it will be executed only once at the next possible time.
If you want to execute a script, for example, every 10 minutes, you can use StartInterval
instead:
key>StartInterval</key>
<integer>600</integer> <
File Location
Once you have written your .plist
file you will have to save it in the appropriate directory. There are multiple different options:
Folder | Usage |
---|---|
/System/Library/LaunchDaemons | Apple-supplied system daemons |
/System/Library/LaunchAgents | Apple-supplied agents that apply to all users on a per-user basis |
/Library/LaunchDaemons | Third-party system daemons |
/Library/LaunchAgents | Third-party agents that apply to all users on a per-user basis |
~/Library/LaunchAgents | Third-party agents that apply only to the logged-in user |
Agents are always associated with the logged-on user, meaning the scripts are restricted to only a specific user, while Deamons are run under the root user and thus run for everyone.
Since my script specifically saves data in a folder associated with my user, it is better to use an Agent and not a Daemon. Therefore, I chose the lowest option as my location for the .plist
file: /Users/user/Library/LaunchAgents/com.jpq.download_data_from_sheets.plist
Loading an Agent
After you have saved your .plist
file in one of the appropriate locations, you have to load it using the launchctl
command in the terminal.
launchctl load /Users/user/Library/LaunchAgents/com.jpq.download_data_from_sheets.plist
If you want to see all loaded jobs, you can use launchctl list
in the terminal. In order to stop a job, use launchctl unload file/path
. Using launchctl start JobLabel
will run the job immediately and not only when scheduled.
This is it! Now our script will be executed automatically at the specified time points.