When we write a lot of scripts we will eventually want to automate some tasks like saving Database backups, scrap a website periodically, do POST or GET requests to an API, etc. In all these cases we will need a task scheduler. On a Linux systems, our easiest and best option is Crontbab and while it is a great option for quickly creating a scheduled task both on Linux and Mac, Apple deprecated it in favor of launchd. This does not mean that we can’t use Crontab
on Mac but the downside of Crontab
is that is assumes your machine is awake and therefor will skip the task until the next time if this is not the case. On the other hand, launchd
will not run while your Mac is asleep BUT once you wake it up again, it will run.
Nonetheless, both are great and thus we will cover them both. These are the main reasons we would want to use Crontab
over launchd
:
- It is simpler to setup
- We don’t care if the job runs every time, it just should run on a best effort basis
Index:
Crontab
As mentioned above Crontab is simpler to setup, it comes already installed and all we have to do is add our job to the list which we can access using the -l
flag:
$ crontab -l
This lists all our jobs which currently is probably empty.
In order to add a job we use the -e
flag:
$ crontab -e
This will let us add a job to the list. The default editor on Mac is VIM but don’t be afraid. Here is a very quick guide to VIM. When the VIM editor opens:
- Use the
i
key to start insert mode and edit the file - Use the arrow keys to move around the cursor
- Use the
esc
key to stop editing - Type
:wq
to write your changes and exit after theesc
key
Now lets get into the actual job running:
$ 1 2 3 4 5 node my_node_job.js
Crontab lets you specify the time by putting your desired times in the right slots as followed (based on the above job):
- 1) minute (0 - 59)
- 2) hour (0 - 23)
- 3) day of month (1 - 31)
- 4) month (1 - 12)
- 5) day of the week (0 - 6)
For example a tasks that will run every day at 4pm:
0 16 * * * node my_node_job.js
If you don’t specify a day, month or day of the week, it will run every day. If we take a look at our job list again, we can see our newly created job:
$ crontab -l
0 16 * * * node my_node_job.js
A little extra to note, if we leave the cronjob as it is, we will get an email in our terminal every time it executes which can be a little annoying. The fix is fairly simple, open the job list again and add at the top this line:
MAILTO=""
0 16 * * * node my_node_job.js
No more email great ^^
Launchd
While Crontab is a quick solution for scripts that don’t necessarily have to run every day, sometimes we need something more robust. Sometimes we might be commuting at the time our job should run and as mentioned above, Crontab will skip this job and will not run it until tomorrow at the same time. Launchd solves this problem for us because it knows we are not always actively using our computer.
In order to create a new job with Launchd
we have to create our .plist
file first which is just an XML
file with some boilerplate XML from Apple’s docs. We can create the file using touch
:
$ touch ~/Library/LaunchAgents/org.USERNAME.TASK-NAME.plist
Using the Crontab example, note my username is Alexander
you have to use yours:
$ touch ~/Library/LaunchAgents/org.Alexander.my-node-job.plist
Now we can open it in our favorite editor (mine is Atom):
$ atom ~/Library/LaunchAgents/org.Alexander.my-node-job.plist
This is the boilerplate XML we need for any job:
<?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>org.Alexander.my-node-job</string>
<key>ProgramArguments</key>
<array>
<string>/Users/alexander/.nvm/versions/node/v8.4.0/bin/node</string>
<string>/Users/alexander/Desktop/my-node-job.js</string>
</array>
</dict>
</plist>
At this point we have two options to schedule our job:
- Run it in intervals (every 10 seconds, 1 minute, etc.)
- Run it a specific times (4pm every day)
If we want to run it in intervals we have to add an interval key to our XML specifying the interval in seconds like this:
<?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>org.Alexander.my-node-job</string>
<key>ProgramArguments</key>
<array>
<string>/Users/alexander/.nvm/versions/node/v8.4.0/bin/node</string>
<string>/Users/alexander/Desktop/my-node-job.js</string>
</array>
<key>StartInterval</key>
<integer>60</integer> <!-- 60 seconds or 1 minute -->
</dict>
</plist>
This would run every 60 seconds. If we want to run our job based on a specific time we exchange the interval
key for a calendar interval
key:
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>5</integer>
<key>Hour</key>
<integer>5</integer>
<key>Minute</key>
<integer>5</integer>
</dict>
In our case all we want is to specify the hour since we want to run our job every day at the same time:
<?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>
<!-- The label should be the same as the filename without the extension -->
<string>org.Alexander.my-node-job</string>
<key>ProgramArguments</key>
<array>
<string>/Users/alexander/.nvm/versions/node/v8.4.0/bin/node</string>
<string>/Users/alexander/Desktop/javascript_box/nodejs/cronjob.js</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>16</integer>
</dict>
</dict>
</plist>
Great now all that is left is to load our agent (job). Here you have 2 options, you can do it the manual way or use a small tool for this called lunchy
:
The Manual way
We have to use launchctl
to load and start our job (note, replace Alexander with your username):
$ launchctl load ~/Library/LaunchAgents/org.Alexander.my-node-job.plist
$ launchctl start org.Alexander.my-node-job
Using Lunchy
Lunchy is a Ruby gem so we have to install it first like this:
$ gem install lunchy
Once it is downloaded we can load our job a little more simply, we don’t have to specify a path nor the username just the plist’s name:
$ lunchy restart my-node-job
Similarly to list our jobs:
$ launchctl list
$ lunchy list
Finally to stop our Agent:
$ launchctl stop org.Alexander.my-node-job
$ lunchy stop my-node-job