How to Use launchd to Run Scripts on Schedule in macOS

launchd is a tool for starting, stopping and managing scripts and processes. If you’ve worked with any version of Linux, you might be familiar with cron. launchd is basically cron in macOS.

launchd-scripts-maxwells-demon

Daemons (pronounced “demons”) are scripts that run in the background. Unlike applications, daemons run as processes and are not under the direct control of the user or another application. On macOS they’re under the command of the launchd framework which decides when they start and stop.

The unusual name comes from Maxwell’s demon, an imaginary agent that sorts molecules in a thermodynamics thought experiment.

How to Run a Python Script in Mac OS X

In order to run daemons through launchd, you’ll need to write some scripts. The most common scripting language is bash. If you want to learn more about bash scripting, you can check out our beginner’s guide to bash scripting.

launchd-scripts-job-description

Scripts in launchd are triggered by job definitions which are .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 be run. 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" "http://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>

Modify as necessary, then put it in a text file with the .plist extension before dropping it 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 written in reverse domain notation, and “local” is a great domain for private agents.
  • Program: the full path of the script this job description launches.
  • 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.
    • StartInterval: start the job every n seconds. This example will run the job every 7200 seconds or every 2 hours.
      <key>StartInterval</key> 
      <integer>7200</integer>
    • StartCalendarInterval: run the job at a specific time and date. The below code 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>

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.

launchd-scripts-launchctl-list

Once you’ve created your scripts and saved your agent into the right place, you’ll need to load it into launchctl. This will happen automatically on logins in the future.

To see what’s currently running in laucnhctl, you can use launchctl list in the terminal. This giant list can be grepped for your script by labeling it with something like the following:

launchctl list | grep local.restart

To load a script, open up Terminal and use the following command:

launchctl load ~/Library/LaunchAgents/local.restart.plist

launchd-scripts-launchctl-load

To remove the script from the launchctl queue, use the unload command:

launchctl unload ~/Library/LaunchAgents/local.restart.plist

launchd-scripts-launchctl-unload

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 a 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 launchctl.

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 appears. To learn a lot more about launchd, you can check out the laucnhd tutorial.

Image credit: Maxwell’s_demon

Leave a Reply

Yeah! You've decided to leave a comment. That's fantastic! Check out our comment policy here. Let's have a personal and meaningful conversation.