I'm trying to configure launchd to trigger a python script/unix executable (i.e. python script with shebang line). When I load the .plist file (below), launchctl
shows a status of 127 meaning, "The specified service did not ship with the operating system". However when I copy and paste the value I've entered for 'program' in the .plist file into the mac terminal it runs fine.
I've redirected stdout/stderr to the terminal (via the .plist) and it returns the message,
$ env: python3: No such file or directory
If I replace the value of Program
in the plist to a simple 'hello world' esque batch script it works fine.
Why does the python program (urlwatch) run fine in terminal but return an error when called via launchd? How do I fix it?
Plist file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.careersWatch3</string>
<key>Program</key>
<string>/Users/justinmichael/Documents/urlwatch-master/urlwatch</string>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/dev/ttys000</string>
<key>StandardErrorPath</key>
<string>/dev/ttys000</string>
</dict>
</plist>
I eventually want to run the script at set times of the day, but for now I'm using RunAtLoad
= true
for testing purposes until I can get it to work.
Loading into launchd and output:
$ launchctl load ~/Library/LaunchAgents/local.careerswatch3.plist
$ env: python3: No such file or directory
Call to check status of agent and output:
$ launchctl list | grep local.careersWatch3
- 127 local.careersWatch3
Looking up the meaning of code '127' in terminal:
$ launchctl error 127
127: The specified service did not ship with the operating system
The problem was with the environment variables, specifically that the $PATH is different for jobs run by cron vs programs called by me in terminal as a logged in user. Calling echo $PATH
in a cron job and checking if it includes the directory of the python interpreter can help to confirm this is the problem.
Two solutions:
1) Quick and Dirty
Find where the Python interpreter is installed and change the shebang line at the top of the unix executable/python script to call it directly. i.e
#!/usr/bin/env python3
becomes
#!/usr/local/bin/python3
Here, it doesn't matter if the python interpreter is on the path or not because it's location is given explicitly. The downside is that its location is now hard-coded and if you move the script to a different computer the script may not work, neither in cron nor when run in the terminal, if python has been installed in a different location.
2) Less Quick, Less Dirty
Write a shell script that adds the location of the python interpreter to the path if it isn't already there (as per this SuperUser question) and then calls the python script. This way the script hasn't been changed and won't be accidentally broken by moving it to a computer where python is installed in a different directory.
#!/bin/bash
# directory python is found in
dir="usr/local/bin"
#add to path if not there
if [ -d "$dir" ] && [[ ":$PATH:" != *":$dir:"* ]]; then
PATH="${PATH:+"$PATH:"}$dir"
fi
#Run program
/path/to/program/program_name
Be sure to make the script executable via chmod +x /path/to/script/script.sh
Local articles referenced by this article: