When we want to run a job at regular intervals for eg: Run a task every few seconds or minutes.
We can also instruct a mac to run a task on a particular day. We do this using a progarm named launchd. There are other way such as cron to acheive the same result. launchd is the preferred way to execute these long running tasks. For eg: I have a script that runs every 4 hours and check the price of somethign I want to buy one. The script then alerts me each time there is change in price.
These long running tasks are either called Daemons or Agents.
Daemons vs Agents
The dictionary defines a Dameon as a background process that handles requests for services. A Daemon is dormant when not required.
Although the exact origin of the name Daemon is not clear, it is could have been derived from “disk and execution monitor” or “device monitor”
Here we refer to any long running background service that is invoked at a user defined interval or time as a Daemon or Agent.
In the context of launchd there are two flavours of such long running background jobs.
1) Daemon - A system wide service of which there is one instance for all clients.
2) Agent - A service that is runs on per user basis.
Here we only deal with Daemons.
Introducing launchd
Once we create a Deamon, a program called launchd takes care of running the it. launchd is a system wide and per-user daemon/agent manager
launchd is one of the first programs that is started when your mac boots and it has a role to play in almost all apps that run on your mac.
Perphas the most important thing to know about launchd is that the program cannot be invoked direclty. We do that using another program called launchctl.
Introducing launchctl
The man page defines launchctl as a progarm that “interfaces with launchd to manage and inspect daemons, agents and XPC services.”
We will mostly work with launchctl.
Lets create a simple Daemon and see how things work.
Creating a simple Daemon
Lets first create a simple tasks that we can ask launchd to execute.
Instruction 1 : Create a file named simple-script.sh at any location. Type in Code 1.1
Code 1.1 : Create a simple shell script
1
echo "Hello World" >> /Users/<user name>/Documents/output.txt
Note : Dont forget to replace <user name>
with your user name. You can get user name by running id -un
command in terminal.
To execute this script, open the terminal app and type in command on line# 1. This script will create a new file called “Output.txt” and will write the string “Hello World” in it.
Instruction 2 : Give the execution permission
Code 2.1 : Add permission to execute our shell script.
1
chmod +x simple-script.sh
Note : You might need to run the command in code as an administrator using sudo
command.
Instruction 3 : Execute simple-script.sh
To execute this script, open the terminal app and type in command in Code 3.1
``line# 1.
Code 3.1 : Execute simple-script.sh
1
./simple-script.sh
This script will create a new file named “output.txt” and write the string “Hello World” to it.
We can now ask launchd to run this script at regular intervals. As mentioned earlier we cannot interact direclty with launchd we pass instructions using launchctl.
We pass instructions to launchd using a plist file.
Here is a sample plist
Code 4.1 : simple plist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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.prash.simple-script</string>
<key>ProgramArguments</key>
<array>
<string>/Users/<user-name>/Documents/simple-script.sh</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
</dict>
</plist>
Code 4.1
is a simple plist. It has three required fields.
- Label - A unique name for daemon. As a convention we use the reverse dns format and the label has the same name as the plist file (com.demo.simple-script)
- ProgramArguments - Full path to the script we want launchd to run.
- StartInterval - When launchd should run the daemon. In this example, the daemon will run every 60 seconds.
Next we need to pass this plist to launchd.
launchd picks up daemons from folder named /Library/LaunchDaemons/
Note : User Agents are placed in a folder named /Library/LaunchAgents/
Instruction 5 : Copy our plist into the /Library/LaunchDaemons/
Code 5.1 : Copy plist ino LaunchDaemons folder
1
sudo cp /Users/<user-name>/Documents/com.demo.simple-script.plist /Users/LaunchDaemons/com.demo.simple-script.plist
Instruction 6 : Load the plist. Next we instruct launchd to load the plist. launchd will now load and run the daemon.
Code 6.1 : Load the deameon
1
sudo launchctl load /Users/LaunchDaemons/com.demo.simple-script.plist
Note : We are looading the plist from /Users/LaunchDaemons/com.demo.simple-script.plist and NOT the Documents folder where created the plist.
We can check if our daemon loaded correclty using the launchctl list
command.
1
sudo launchctl list
To filter out only our daemon.
1
sudo launchctl list | grep com.demo
If we head over to the /Users/Documents/output.txt file, you should see the file updating every 60 seconds.
Stoppping a dameon.
To stop a daemon use launchctl’s unload command.
Code 7.1 : Unload the deameon
1
sudo launchctl unload /Users/LaunchDaemons/com.demo.simple-script.plist
Note : the unload command above only stops launchd frmm invoking our daemon.
Cleaning up : If we dont plan to run a daemon it is a good idea to remove the corresponding plist from the frmm the /Library/LaunchDaemons folder.
Code 8.1 : Unload the deameon
1
sudo rm /Users/LaunchDaemons/com.demo.simple-script.plist