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 -lThis lists all our jobs which currently is probably empty.
In order to add a job we use the -e flag:
$ crontab -eThis 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
ikey to start insert mode and edit the file - Use the arrow keys to move around the cursor
- Use the
esckey to stop editing - Type
:wqto write your changes and exit after theesckey
Now lets get into the actual job running:
$ 1 2 3 4 5 node my_node_job.jsCrontab 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.jsIf 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.jsA 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.jsNo 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.plistUsing the Crontab example, note my username is Alexander you have to use yours:
$ touch ~/Library/LaunchAgents/org.Alexander.my-node-job.plistNow we can open it in our favorite editor (mine is Atom):
$ atom ~/Library/LaunchAgents/org.Alexander.my-node-job.plistThis 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-jobUsing Lunchy
Lunchy is a Ruby gem so we have to install it first like this:
$ gem install lunchyOnce 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-jobSimilarly to list our jobs:
$ launchctl list
$ lunchy listFinally to stop our Agent:
$ launchctl stop org.Alexander.my-node-job
$ lunchy stop my-node-job