I am attempting to set up a .plist file for use with launchctl on OSX. When run from the command line (not using launchctl), our application is executed as:
/path/to/ourapp
... and to terminate our application, we type:
/path/to/ourapp -k
... which causes the new instance of ourapp to properly kill the previous instance that is running.
Now I set up a .plist file to control execution of the application via launchctl, as follows:
// start.plist
<dict>
<key>Disabled</key>
<false/>
<key>Label</key>
<string>ourapp</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/ourapp</string>
</array>
<key>OnDemand</key>
<false/>
<key>UserName</key>
<string>daniel347x</string>
<key>SHAuthorizationRight</key>
<string>system.preferences</string>
</dict>
... and I execute it at the command line, as follows:
launchctl load /path/to/start.plist
This works successfully to launch the application.
Unfortunately, when creating a stop.plist, as follows (the only difference is the addition of the -k argument):
// stop.plist
<dict>
<key>Disabled</key>
<false/>
<key>Label</key>
<string>ourapp</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/ourapp</string>
<string>-k</string> // <-- only difference is adding this argument
</array>
<key>OnDemand</key>
<false/>
<key>UserName</key>
<string>daniel347x</string>
<key>SHAuthorizationRight</key>
<string>system.preferences</string>
</dict>
... and executing via
launchctl unload /path/to/stop.plist
... the application does NOT terminate ... therefore, it seems that when using launchctl, the application is not being executed equivalently to
/path/to/ourapp -k
Can someone tell me what launchctl unload is really doing - i.e. is it calling the application from the command line with the given arguments, or not - and what I can do to get my stop.plist to work when using launchctl unload, given whatever it is doing?
High level
No, "launchctl unload" on OSX does not pass "ProgramArguments" to the target application. It removes the plist from the ones that launchd is running.
Short answer
I'm not sure you are using launchctl and launchd the way it was designed, but try:
launchctl load /path/to/stop.plist
Longer answer
The launchctl command is used to control which plists launchd runs at any given time. When you issue:
launchctl load /path/to/start.plist
the plist is loaded into launchd and your "ourapp" is trigged. A critical point is that unless "ourapp" unloads the start.plist, it will still be running in launchd. You can check this by viewing the output from:
launchctl list
The reason your call to unload the stop.plist didn't have any effect is that it was never loaded into launchd to begin with. To be able to stop it, you must first start it with:
launchctl load /path/to/stop.plist
The trick with this is that once you do this, both your start.plist and stop.plist will be running. Obviously, I haven't tested your app/setup, but this could cause weird behavior. Also, I'm not sure what running the "load" again on your start.plist would do since it would already be running.
Form what you've described here, it looks like launchd and launchctl might not be the best approach. If you want to stick with it, I think you would need to add system calls into your "ourapp" so that when it gets issued the kill command it also unloads both the start and the stop plist. Or, write a proxy script that unloads the plists and then calls your "ourapp -k". That way, the next time you use "launchctl load /path/to/start.plist" you won't be trying to start something that's already running.
One other note. You might have just been using the "ourapp" as place holders, but it's worth pointing out that you probably want to use unique Label names. If nothing else, it'll help you keep track of them with "launchctl list". I don't know if launchd itself would have an issue with duplicate names, but it seems safest to avoid the possibility.
Related
Is there a way to detect if audio is being output from the Mac system? Whether it's via headphone jack, usb-c, or bluetooth, is there a process which runs if and only if audio is playing?
I've made a unix script which plays a 15hz sine waveform, the idea is to execute the script every 580 seconds if and only if no audio is playing. If audio is playing, the script won't execute.
I have really good speakers but the drawback is they enter a 'standby' power saver mode every 10 minutes (600 seconds). When I'm at work I don't really care about whether it runs in the background or not* but when I'm at home I tend to miss notifications when speakers enter standby, so the aim of the script is to play the 3 second waveform every 580 seconds if and only if no audio is playing.
I'm not sure if this is super related to what you want but maybe try out Soundflower ? It's super useful for re-routing audio in mac.
You do not necessarily need to use AppleScript to accomplish the goal, although you certainly can if you want.
I'd opt to use a Launch Agent, as in the following example XML plist code:
<?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.my.play.if.no.audio.is.playing</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>[[ $(pmset -g | grep ' sleep') =~ coreaudiod ]] || '/path/to/shell_script'</string>
</array>
<key>RunAtLoad</key>
<false/>
<key>StartInterval</key>
<integer>580</integer>
</dict>
</plist>
Change '/path/to/shell_script' in the XML plist code to the actual fully qualified pathname of your shell script.
Then save is as, e.g., com.my.play.if.no.audio.is.playing.plist in ~/Library/LaunchAgents/ and load it from Terminal using, e.g.:
cd ~/Library/LaunchAgents/
launchctl load com.my.play.if.no.audio.is.playing.plist
Then every 580 seconds your shell script will execute if there is no audio playing.
Notes:
As coded, it assumes the executable bit is set on your shell script.
If you are going to use Launch Agents and Launch Daemons, I highly recommend you read the manual pages for launchctl, launchd.plist and launchd.
You can read the manual page for command in Terminal by typing man command, then press enter, or for easier reading, just type command and then right-click on it and select: Open man Page
To stop your Launch Agent, in Terminal:
cd ~/Library/LaunchAgents/
launchctl unload com.my.play.if.no.audio.is.playing.plist
You could also instead, include the test condition in your shell script, e.g.:
#!/bin/bash
if [[ ! $(pmset -g | grep ' sleep') =~ coreaudiod ]]; then
# Code to generate your sine waveform goes here.
fi
Then, the command used in the example XML plist code would be:
<key>ProgramArguments</key>
<array>
<string>/path/to/shell_script</string>
</array>
If you really want to use AppleScript, and assuming a Stay Open application, then:
Example AppleScript code:
on run
-- # Add any AppleScript code you what run when the
-- # Stay Open AppleScript application is opened.
end run
on idle
do shell script ¬
"[[ $(pmset -g | grep ' sleep') =~ coreaudiod ]] || '/path/to/shell_script'"
return 580
end idle
on quit
continue quit
end quit
Notes:
When saving the example AppleScript code in Script Editor , set File Format: [Application] and [√] Stay open after run handler.
If your add the test condition to your shell script, as shown further above, then your do shell script command would be:
do shell script "'/path/to/shell_script'"
I tend to stay away from using this type of AppleScript application, as I find that over time they can/may become resource intensive in that they continue consume more RAM. YMMV.
MacOS High Sierra
I want to run this script on startup, login and shutdown, logoff.
The script looks like this and is working successfully from terminal:
#!/bin/sh
find /private/my/tmp -mindepth 1 -maxdepth 1 -type d -not -name -specialfolder -exec rm -r {} \;
find /private/my/tmp -mindepth 1 -maxdepth 1 -exec rm -r {} \;
I have used the Automater to create an .app placed my script in Login Items and this works when logging in, however this does not solve the issue of startup, logoff or shutdown so I do not think that this is the answer.
Reading around, it seems I need to use launchd, but I really can't figure out how to do this with my script.
Can anyone please help?
Edit: For using with launchd, how does this look? I am sure to have some mistakes, hopefully someone can guide me in the right direction.
<?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>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:</string>
</dict>
<key>Label</key>
<string>com.startup</string>
<key>Program</key>
<string>/private/myscripts/startup.sh</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>LaunchOnlyOnce</key>
<true/>
</dict>
</plist>
To run the script at both login and logout, you can wrap it in an AppleScript applet.
This AppleScript code will run the script you specify at both startup and termination:
to run_script()
do shell script "/path/to/script.sh"
end run_script
on run
run_script()
end run
on quit
run_script()
continue quit
end quit
Save this in Script Editor.app as an "Application", checking the checkbox labelled "Stay open after run handler". Now, the applet will remain open until manually quit or a logout occurs.
The applet will appear in the Dock; to disable that, add LSUIElement or LSBackgroundOnly keys to the applet's Info.plist. Those are well-documented keys that Google can tell you more about.
Now, finally, we need to make it run at login. You can do this either using Login Items or a launchd plist. Your example plist above should work, provided you point it at the applet's executable in, e.g., App.app/Contents/MacOS/.
Since there is no "Year" parameter in the launchd plist format, how is it possible to create an agent that will run only once and not once a year?
Since your reference "Year", I assume you are using StartCalendarInterval.
If you don't want to run at an interval, use the RunAtLoad key, which will cause it to run once each time the system starts.
<key>RunAtLoad</key>
<true/>
Now if you truly only want the script to run once (ever), don't use launchctl at all, just run it once on the command line and be done with it.
Use the key LaunchOnlyOnce for this:
<key>LaunchOnlyOnce</key>
<true/>
This will launch the agent only once if the system is not rebooted.
I am looking for the solution how to open the website at the certain time. Considering the shell programming, it is possible to open website by the command:
$ open http://stackoverflow.com
How to do it at the certain moment if it is possible at all?
You can use crontab to make it.
The crontab is a list of commands that you want to run on a regular
schedule, and also the name of the command used to manage that list.
crontab stands for "cron table," because it uses the job scheduler
cron to execute tasks; cron itself is named after "chronos," the Greek
word for time.
Say You have a script /bin/openURL.sh to open a website,
30 21* * * /bin/OpenURL.sh
means executing it 21 : 30 every day.
More usage about crontab, see http://www.computerhope.com/unix/ucrontab.htm
For one-time jobs (scheduled tasks), at commands are simple to schedule, as demonstrated by John1024's since-deleted answer, but at has drawbacks on OSX:
It must be enabled first; this is a one-time operation: sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist.
You must use sudo (have administrative privileges) to be able to schedule job with at.
If a job produces any output, it will be mailed to the user using mail, as stdout and stderr output (I don't know how to suppress this).
Example: schedule opening http://stackoverflow.com once, at 19:00 (7 PM):
sudo bash -c 'echo "open http://stackoverflow.com" | at 19:00'
Using crontab for periodic jobs is an option on OSX; e.g., to schedule a job that opens http://stackoverflow.com every day at 19:00 (7 PM):
Run crontab -e to open the current user's cronfile in your editor.
Add the following line, save, and close the file:
0 19 * * * open http://stackoverflow.com
If a job produces any output, it will be mailed to the user using mail, as combined stdout and stderr output (I don't know how to suppress this).
However, the official recommendation on OSX is to use launchd for both one-time and periodic jobs:
launchd is very flexible and centralizes all job scheduling; as with crontab, there are system-wide and per-user jobs.
the downside is that the .plist files required to define jobs are cumbersome and non-trivial to create.
Using our previous example:
One-time job: (opens http://stackoverflow.com once, at 19:00 (7 PM))
Create file ~/test.plist (for a one-off job, the location doesn't matter).
Paste the following:
<?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>TestJob</string>
<key>ProgramArguments</key>
<array>
<string>open</string>
<string>http://stackoverflow.com</string>
</array>
<key>LaunchOnlyOnce</key>
<true/>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>19</integer>
<key>Minute</key>
<integer>00</integer>
</dict>
</dict>
</plist>
TestJob uniquely identifies your job.
Setting LaunchOnlyOnce to true ensures that the job is only run once.
From Terminal, run launchctl load ~/test.plist to load the job.
Periodic job: (opens http://stackoverflow.com every day at 19:00 (7 PM))
Create file ~/Library/LaunchAgents/testPeriodic.plist
Note: The location matters: ~/Library/LaunchAgents is where per-user job-definition *.plist files must reside in order to be loaded automatically at every logon.
Paste the following:
<?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>TestJobPeriodic</string>
<key>ProgramArguments</key>
<array>
<string>open</string>
<string>http://stackoverflow.com</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>19</integer>
<key>Minute</key>
<integer>00</integer>
</dict>
</dict>
</plist>
Again, TestJobPeriodic uniquely identifies your job.
Jobs runs periodically by default (i.e., the absence of LaunchOnlyOnce makes the job periodic).
From Terminal, run launchctl load ~/Library/LaunchAgents/testPeriodic.plist to load the job.
For background information, see https://stackoverflow.com/a/23880156/45375.
i am looking for a way to periodically re-launch an application in OS X. since this application has a GUI i chose the LaunchAgent method to do it. the LaunchAgent will call a shell script, which will quit the application (if running) and then open it again.
so far it works, with the only exception that once my called shell script has ended, the launched GUI application is also quit.
i tried opening the application with trailing "&" and using nohup, but no luck.
this is my LaunchAgent (ignore my stupid StartCalendarInterval settings, they are for debugging only):
<?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.mycompany.myapp_relauncher</string>
<key>ProgramArguments</key>
<array>
<string>/Users/xyz/Desktop/myapp_relauncher.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Minute</key>
<integer>21</integer>
<key>Minute</key>
<integer>25</integer>
</dict>
</dict>
</plist>
and this is the called script /Users/xyz/Desktop/myapp_relauncher.sh
#!/bin/bash
function log { echo "myapp: "`date "+%Y%m%d-%H%M%S"`" $1" ; logger -t "myapp" "$1" ; }
# stopping the app if running (code ommitted here)
log "starting application now..."
nohup /Applications/myapp.app/Contents/MacOS/myapp &
log "sleeping a few seconds now..."
sleep 10 # the launched applications stays open
log "done sleeping, quitting."
# script ends, launched application ends too
Add <key>AbandonProcessGroup</key><true/> to your LaunchAgent .plist; without that, when the agent (the script) exits launchd "helpfully" cleans up all leftover subprocesses (technically, anything with the same process group ID), like for example the application.
You want to use disown here. This works without closing the application after the script finishes:
#!/bin/bash
/Applications/Adium.app/Contents/MacOS/Adium &
disown
This is because using & just puts the job into the background. Even though it's in the background it is still a child process of the current script, which is killed when its parent process, the script, finishes. By using disown that relationship is removed and it runs on its own.
You might also just use the open command:
killall myapp; open -jga myapp
open -jg usually opens an application hidden and without raising any windows. If for example TextEdit is open but has no open windows, -jg creates and raises a new default window though. To avoid it, you can test if the application is already open:
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>-c</string>
<string>pgrep -x TextEdit||open -jga TextEdit</string>
</array>