Run a shell script periodically on Mac OS X without root permission

user3671325 picture user3671325 · May 26, 2014 · Viewed 13.8k times · Source

I want to start up a file with .sh type or .py on mac os x without using root , I searched in google and found launchctl can help me ,

so i read tutorial and do same in tutorial but it not work for me , [i using mac os x 10.9 x64]

My .plist file [run 1.sh file in every 60second] :

<?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.alvin.crontabtest</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/paul/Desktop/1.sh</string>
  </array>
  <key>Nice</key>
  <integer>1</integer>
  <key>StartInterval</key>
  <integer>60</integer>
  <key>RunAtLoad</key>
  <true/>
  <key>StandardErrorPath</key>
  <string>/tmp/AlTest1.err</string>
  <key>StandardOutPath</key>
  <string>/tmp/AlTest1.out</string>
</dict>
</plist>

source of 1.sh:

echo '+' >> /Users/paul/Desktop/worked.txt

I put Run.plist in /Users/paul/Run.plist

and run command from terminal :

launchctl load /Users/paul/Run.plist
Launchctl start com.alvin.crontabtest

commands execute without any error but i not see anything in worked.txt

can anyone help me please ?

Answer

mklement0 picture mklement0 · May 27, 2014

To clarify: The OP's .plist file itself was perfectly OK - the problem turned out to be inside the shell script invoked (not shown in the question).

On OS X, using .plist files loaded by CLI launchctl and invoked by daemon manager launchd is the preferred way of scheduling (recurring) tasks (see below for more).

Things to note:

  • The format of launchd .plist files is described in man launchd.plist
  • For a .plist file to be loaded every time the current user logs in, it must be placed in ~/Library/LaunchAgents/ (all-users files must be placed in /Library/LaunchAgent/ - requires root privileges).
  • Specifying output-capture files with keys StandardOutPath and StandardErrorPath means that successive invocations append to the files specified, which means that these files keep growing indefinitely, unless managed externally.
  • Re troubleshooting: @Grady Player's advice applies: launch Console.app and look for com.apple.launchd.peruser entries - a failure to invoke the command specified in the .plist would show there.

@ghoti's answer describes a general Unix alternative to launchd, cron (typically used on Linux):

As for how cron relates to OS X: @ghoti asks:

Any particular reason you don't want to use a normal crontab?

On OS X, man crontab advises (emphasis added):

Although cron(8) and crontab(5) are officially supported under Darwin [OS X], their functionality has been absorbed into launchd(8), which provides a more flexible way of automatically executing commands. See launchctl(1) for more information.

The bottom line is this:

  • If you come from a *nix background, you may be more comfortable with continuing to use cron and crontab, assuming:
    • you're aware of the fact that additional background tasks, scheduled via launchd, may exist.
    • you're aware of cron's limitations and can work with/around them.
  • Otherwise, on OS X:
    • many third-party apps use the native launchd feature and thus specify periodic background tasks via .plist files in /Library/LaunchAgents (for all users) or ~/Library/LaunchAgents (for the current user).
    • If you want to centralize management of background tasks to these locations and/or you want to take advantage of the increased flexibility that launchd provides, go with specifying background tasks via .plist files evaluated by launchd.

Adding simple cron tasks is probably simpler than creating .plist files for launchd, but 3rd-party utilities such as Lingon 3 can help with the latter.

Also, there are subtle differences in how cron tasks are invoked vs. per-user launchd tasks: for instance, the former do not allow user interaction via AppleScript, whereas the latter do.


Case in point re increased flexibility of launchd: the OP, in a follow-up comment, asks for the task to run every 30 seconds:

  • The minimum interval for cron tasks is 60 seconds, necessitating the workaround in @ghoti's answer.

  • By contrast, with the launchd .plist file, changing <key>StartInterval</key><integer>60</integer> to <key>StartInterval</key><integer>30</integer> is enough.