Okay, ganz einfach, ich habe ein Shell-Skript, das warten muss, bis etwas passiert, aber es hat eine Sperrdatei und einige untergeordnete Prozesse, die ich sicherstellen muss, dass sie aufgeräumt werden, wenn das Skript unterbrochen wird.
Ich habe dies ohne Probleme erreicht, indem ich mit dem trap
Befehl einige geeignete Aktionen festgelegt habe, und habe ein Skript entwickelt, das ungefähr so aussieht:
#!/bin/sh
LOG="$0.log"
# Create a lock-file to prevent simultaneous access
lockfile -l 86400 "$LOG.lock" || $(echo 'Locking failed' >&2 && exit 3)
# Create trap for interrupt and cleanup
on_complete() {
echo $(date +%R)' Ended.' >> "$LOG"
kill $(jobs -p)
rm -f "$LOG.lock"
exit
}
trap 'on_complete 2> /dev/null' SIGTERM SIGINT SIGHUP EXIT
# Do nothing
echo $(date +%R)' Running…' >> "$LOG"
sleep 86400 &
while wait; do sleep 86400 &; done
Dies kann problemlos in einem Terminal über ausgeführt sh Example.sh
und mit beendet werden Ctrl + C
, wodurch die Sperrdatei ohne großen Aufwand entfernt wird.
Ich habe dann versucht, einen launchd
Job für dieses Skript wie folgt zu erstellen:
<?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.example</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>~/Downloads/Example.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>EnableGlobbing</key>
<true/>
</dict>
</plist>
Durch das Erstellen von Example.sh und Example.plist aus dem obigen Ordner im Ordner kann ich den Job über ~/Downloads
ausführen und über beenden . Das Beenden des Jobs führt jedoch nicht dazu, dass a das Skript erreicht, das stattdessen nach dem 20-Sekunden-Timeout ausgeführt wird.launchd
launchd load ~/Downloads/Example.plist
launchd unload ~/Downloads/Example.plist
SIGTERM
SIGKILL
Was ich also wissen möchte, ist; Warum empfängt mein Skript nicht SIGTERM
und wie kann ich sicherstellen, dass dies der Fall ist?
Das ultimative Problem dabei ist, dass Bash seine nicht eingebauten Kinder normalerweise nicht tötet.
If bash is waiting for a command to complete and receives a signal for which a
trap has been set, the trap will not be executed until the command completes.
When bash is waiting for an asynchronous command via the wait builtin, the
reception of a signal for which a trap has been set will cause the wait builtin
to return immediately with an exit status greater than 128, immediately after
which the trap is executed.
Wenn du drückst, <CTRL>+<C>
tötest du das Shell-Skript, das sich normal verhält – aber der Schlaf lebt weiter. Verwenden Sie ps
, um zu sehen.
Wenn Sie versuchen, die Dinge extern zu stoppen, über kill
, dann Bash wie oben. Nach einer gewissen Zeitüberschreitung (ich schätze 20 Sekunden) launchd
wird dann ein ausgegeben, kill -9
das das Skript nicht abfangen kann.
Die Lösung besteht darin, nach dem Ruhezustand ein Wait auszugeben, um Bash anzuzeigen, dass es sich selbst unterbrechen kann:
sleep 86400 & wait
Dadurch kann das Skript unterbrochen werden, aber der Ruhezustand bleibt bestehen. Ich bin mir sicher, dass es einen Weg gibt, die Kinder zu töten, aber ich habe mich nicht darum gekümmert, danach zu suchen ...
wait
hilft nicht (das mache ich in dem eigentlichen Skript, das ich zu debuggen versuche, ich habe das Beispiel so angepasst, dass es etwas genauer übereinstimmt), also bin ich mir nicht sicher was ist los.SIGINT
, not INT
) schläft, empfängt das Signal, bevor er entladen wird. Natürlich ist das nicht gut für Mavericks, Mountain Lion usw., aber es ist großartig, dass dies endlich so funktioniert, wie es sollte, also markiere ich, dass dies die richtige Antwort ist, aber es könnte sich lohnen, es zu bearbeiten, da es nur unter 10.10 korrekt funktioniert .Ihnen ist klar, dass Sie gerade ein Codefragment mit uns geteilt haben und es nicht klar ist, was Ihr Daemon eigentlich noch erreichen möchte, außer alle paar Sekunden eine Aktion auszuführen. Also werde ich einige Annahmen treffen, nur basierend auf dem, was Sie geschrieben haben.
Dies sind alles Probleme, die launchd unter Darwin (und damit OS X) besser lösen soll.
Was die Frage(n) mit dem Entladen und SIGTERM betrifft, speziell, wenn Sie unload
Ihrem Launchdeamon ein SIGKILL anstelle eines SIGTERM senden. Wenn Sie den Job nur stoppen oder ihm ein SIGTERM senden möchten, verwenden Sie stop
anstelle von unload
.
Wenn Sie möchten, dass ein SIGTERM gesendet wird, unload
müssen Sie möglicherweise EnableTransactions
. Wenn Sie Bereinigungsaufgaben haben und möchten, dass Ihr Daemon Signale für die Bereinigung und SIGTERM empfängt, sollten Sie dies EnableTransactions
als Teil der launchd-Plist für Ihr Skript festlegen. <key>EnableTransactions</key><true/>
. Dies wird in der Dokumentation unter https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html beschrieben
Aber die drei oben genannten Mechanismen sind unnötig, da launchd ...
Unter Darwin / OS X mit Launchdaemons ist die geeignete Methode zur Implementierung eines Schlafschleifen-Daemons die Verwendung, StartInterval
um in einem Intervall oder StartCalendarInterval
basierend auf bestimmten Zeiten ausgeführt zu werden. Die zusätzliche Verwendung StartCalendarInterval
bietet den Vorteil, dass das System im Ruhezustand eine verpasste Intervallzeit ausführt, anstatt auf das nächste Intervall warten zu müssen, und ist in diesen Situationen im Allgemeinen das, was Sie wollen. Wenn Sie einen Job haben, den Sie nur aufrufen möchten, sollten Sie auch die Verwendung KeepAlive
als Teil der Plist in Betracht ziehen.
Aus dem von Ihnen bereitgestellten Codebeispiel sieht es also so aus, als ob Sie nur alle 86400 Sekunden etwas ausführen möchten. Wenn dies der Fall ist, dann hat launchd einen Mechanismus dafür, den Sie stattdessen verwenden sollten und der Ihre Sperrdatei und Ihren Trap insgesamt überflüssig macht, da launchd so konzipiert ist, dass er all dies automatisch für Sie erledigt. Dieser Mechanismus ist StartInterval
und wenn er definiert ist, wird er Ihren Dämon alle N Sekunden starten. Launchd stellt auch sicher, dass nicht mehrere Kopien Ihres Daemons gestartet wurden.
Dieser Mechanismus wird in den Launchd-Dokumenten unter https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html beschrieben , wo es heißt:
StartInterval <integer>
This optional key causes the job to be started every N seconds. If the system is
asleep, the job will be started the next time the computer wakes up. If multiple
intervals transpire before the computer is woken, those events will be coalesced
into one event upon wake from sleep.
Ihr darwinisiertes Skript ~/Downloads/Example.sh
würde jetzt also ganz einfach so aussehen:
#!/bin/sh
echo $(date +%R)' Running…' # or whatever it is you wanted to do on the interval
Und Ihre Plist würde in etwa so aussehen:
<?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.example</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>~/Downloads/Example.sh</string>
</array>
<key>EnableGlobbing</key>
<true/>
<key>StartInterval</key>
<integer>86400</integer>
<key>StandardOutPath</key>
<string>/mypathtolog/myjob.log</string>
<key>StandardErrorPath</key>
<string>/mypathtolog/myjob.log</string>
</dict>
</plist>
Beachten Sie, dass ich dies auch so angepasst habe, dass die Protokolldateien hier auf Darwin/launchd-ähnliche Weise und nicht im Skript selbst festgelegt werden. (Sie könnten sie natürlich entfernen und in Ihrem Skript behandeln, aber das ist angesichts von launchd nicht erforderlich.)
Program
Ich möchte anmerken, dass Sie dies auch folgendermaßen implementieren könnten :
<key>Program</key>
<string>sh</string>
<key>ProgramArguments</key>
<array>
<string>~/Downloads/Example.sh</string>
</array>
Möglicherweise finden Sie auch http://launchd.info als nützliche Referenz zusammen mit den Apple-Dokumenten zur Funktionsweise von launchd unter https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ Einführung.html
Informationen zu regelmäßig ausgeführten Daemons finden Sie unter https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html#//apple_ref/doc/uid/10000172i-CH1-SW2
launchd
gesendet wird (oder besser gesagt sollte). true wird nicht funktionieren, da Shell-Skripte die vproc-Befehle nicht aufrufen können, und die Einstellung auf false sollte die Standardeinstellung sein. Ich habe versucht, es als falsch einzufügen, um sicherzugehen, aber es scheint nicht zu helfen.
SIGINT
EnableTransactions
EnableTransactions
Funktioniert auch für Signale entsprechend, unabhängig davon, ob der Prozess tatsächlich vproc verwendet oder nicht, dieser Dokumentationskommentar ist im Grunde für tatsächliche Anwendungen, aber er ist unabhängig davon funktional gleich.sudo defaults write com.apple.loginwindow LogoutHook /Users/Shared/logoutHook.sh
) verwenden.SIGINT
mein Skript nicht so wie es ist eingibt; Konnten Sie das Problem reproduzieren, indem Sie den von mir bereitgestellten Beispielcode ausführen? Keine der Einstellungen für EnableTransactions
scheint für mich einen Unterschied zu machen.SIGINT
.Sie sollten nicht festlegen, EnableTransactions
es sei denn, Sie rufen aktiv vproc_transaction_begin
beim Start auf (was Sie nicht direkt von einem Shell-Skript aus tun können), gefolgt von vproc_transaction_end
beim Herunterfahren. Wenn Sie diese Einstellung aktivieren und dann nicht den ersten Aufruf tätigen, wird Ihr Skript als für eine plötzliche Beendigung geeignet markiert.
Sie möchten auch die Protokollierung implementieren, da Sie beim Herunterfahren ziemlich eingeschränkt sind launchd
und sehen müssen, was fehlschlägt und warum!
Wenn Sie diese kombinieren, erhalten Sie Folgendes /Library/LaunchDaemons/org.example.shutdownhook.plist
(an diesem Ort wird es automatisch beim Booten gestartet):
<?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.example.shutdownhook</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>/Library/PrivilegedHelperTools/org.example.shutdownhook.helper</string>
</array>
<key>StandardOutPath</key>
<string>/var/log/org.example.shutdownhook.launchd/launchd.log</string>
<key>StandardErrorPath</key>
<string>/var/log/org.example.shutdownhook.launchd/launchd.log</string>
<key>RunAtLoad</key>
<true/>
<key>EnableGlobbing</key>
<false/>
<key>EnableTransactions</key>
<false/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
und das als /Library/PrivilegedHelperTools/org.example.shutdownhook.helper
:
#!/bin/sh
dolog() {
echo "$(date) ${1}"
}
on_complete() {
dolog "Trap ${1}"
# do stuff
# ...
dolog "I did some stuff."
dolog "Ended."
# kill child (i.e. sleep) processes - launchd does this
# automatically, but useful if running this script directly
kill $(jobs -p)
exit
}
# useful for debugging to see what signals you get;
# better to only trap specific signals in production
for s in {1..31} ;do trap "on_complete $s" $s ;done
while true
do
dolog "Running…"
sleep $(expr $RANDOM / 3277) & wait
done
Das Obige schreibt alle 0-9 Sekunden zum Debuggen in das Protokoll. Verwenden Sie für die Verwendung ohne Debugging sleep $RANDOM & wait
(0-32767 Sekunden Verzögerung) und bewegen Sie sich möglicherweise dolog "Running…"
über die While-Schleife (abhängig davon, ob Sie regelmäßige Pings an das Protokoll senden möchten).
Sie können verwenden , um Nicht-Apple-sudo -Prozesse einschließlich dieses sudo launchctl list | grep -v com.apple
zu sehen , und Sie können die Protokolldatei sehen, um zu sehen, was vor sich geht (z. B. wenn Sie den Prozess beenden, können Sie sehen, dass er wegen neu gestartet wird ).launchd
tail -f
KeepAlive
REFS:
https://www.unix.com/man-page/osx/5/launchd.plist/
https://developer.apple.com/forums/thread/44221?answerId=622576022#622576022
https://stackoverflow .com/a/61909029/795690
https://apple.stackexchange.com/a/284652/113758
Graham Mill
Audiomaurer