DEV Community

Kinjalk Bajpai
Kinjalk Bajpai

Posted on

A Beginner’s Guide to Scheduling Jobs on macOS

So recently I made a Go app that removes the screenshots from the Desktop. It can be found here Screenshot Remover.

I created this app because I often take a lot of screenshots, which quickly clutter up my Desktop. To keep things tidy, the script automatically removes any image on the Desktop that hasn’t been modified in the last 7 days. That gives me plenty of time to save any screenshots I actually need, while keeping the rest from piling up.

Now the next question, how do I save run the executable daily at some specific time?

The obvious solution is cron as that is something known and used by everyone. But while researching, I found that Apple recommends using launchd for timed jobs over cron in OS X.

So here are the steps to run an executable automatically daily at a specific time -

Creating a LaunchAgent (.plist) file

So what is LaunchAgent and what is a plist file?

In Mac OS, launchd is the primary system which is used to manage system services and any processes.

A LaunchAgent is a type of job which is managed by launchd. In simple terms, a LaunchAgent is a process that is run on behalf of the user when that user is logged in.

A .plist file stands for Property List. It is a file format that is used by MacOS for storing structured data.

In our context, a .plist file is the config file that contains information about everything launchd needs to know to manage the job.

With the above details clear, please create a .plist file with the name of com.user.myjob.plist inside the ~/Library/LaunchAgents/ with any name.

Editing the .plist File

Now the next question, how do we use a plist file to trigger an executable?

Turns out, we can write xml code inside a plist file. So let's do that. Paste the following code inside the file -

<?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.user.myjob</string>

    <key>ProgramArguments</key>
    <array>
        <string>/path/to/your/executable</string>
    </array>

    <!-- Run every day at 7:30 AM -->
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>7</integer>
        <key>Minute</key>
        <integer>30</integer>
    </dict>

    <key>StandardOutPath</key>
    <string>/tmp/myjob.out</string>
    <key>StandardErrorPath</key>
    <string>/tmp/myjob.err</string>
</dict>
</plist>
Enter fullscreen mode Exit fullscreen mode

So what does this code do? Lets walk through the important stuff line by line. First we have this -

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
Enter fullscreen mode Exit fullscreen mode

The above code specifies the DTD for a property list and tells macOS to interpret this XML as a plist file.

<dict>
Enter fullscreen mode Exit fullscreen mode

This opens a dictionary. In plist files, this is a key-value map similar to JSON objects.

  <key>Label</key>
  <string>com.user.myjob</string>
Enter fullscreen mode Exit fullscreen mode

Label is basically a unique identifier for this job. Kinda like the job's internal id.

<key>ProgramArguments</key>
    <array>
        <string>/path/to/your/executable</string>
    </array>
Enter fullscreen mode Exit fullscreen mode

Program Arguments defines what program needs to be executed. An array is used here because sometimes someone needs to provides multiple arguments. In our case, we only need to provide the full path of where the executable is stored.

<key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>7</integer>
        <key>Minute</key>
        <integer>30</integer>
    </dict>
Enter fullscreen mode Exit fullscreen mode

This defines when the job should run. In our case, it is set to run at 7:30 AM daily. This dict allows us to specify multiple fields like Hour, Minute, Weekday, Month etc.

<key>StandardOutPath</key>
    <string>/tmp/myjob.out</string>

    <key>StandardErrorPath</key>
    <string>/tmp/myjob.err</string>
Enter fullscreen mode Exit fullscreen mode

How can we verify if this is running as expected or encountering errors? We can do that using keys to redirect the program's output.

The StandardOutPath is used for normal output (stdout) and the StandardErrPath is used for the error messages (stderr).

So once the job is run, we can check out /tmp/myjob.out and /tmp/myjob.err to debug.

So now we have the plist ready to run the job. But we still need one more step.

Load the job with launchctl

So we have the plist file, but we still need to inform launchd to load it. Thankfully it can be done with just one command.

launchctl load ~/Library/LaunchAgents/com.user.myjob.plist
Enter fullscreen mode Exit fullscreen mode

And after this you can use the following command to verify that it has loaded.

launchctl list | grep com.user.myjob
Enter fullscreen mode Exit fullscreen mode

And that’s it! Your job is scheduled successfully. We have configured a plist file to run an executable daily or whenever we want.

Top comments (0)