💾 Archived View for missbanal.net › how-to-manage-shell-daemons captured on 2023-04-19 at 22:22:49. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-03-20)
-=-=-=-=-=-=-
Published the 2022-01-14 on Willow's blog
While writing shell script, it is pretty common to need to manage some daemons.
Example inspired from sxmo-utils where we got a sxmo_modemmonitor.sh script that listen to dbus signals to dispatch notifications.
#!/bin/sh dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \ while read -r line; do notify-send "$line" done & dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \ while read -r line; do notify-send "$line" done &
This script got a huge issue. It will exit itself after reaching the end of the file. That mean you cannot control the two dbus-monitor subprocess anymore. You'll have to kill each of them manually.
#!/bin/sh dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \ while read -r line; do notify-send "$line" done & dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \ while read -r line; do notify-send "$line" done & wait wait
This is a better idea. The two wait will make the script to wait for subjobs to finish. But we still doesnt manage the subprocesses.
If you want to be able to clear subprocesses with Ctrl+c or with a kill signal you will do something like this:
#!/bin/sh dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \ while read -r line; do notify-send "$line" done & PID1=$! dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \ while read -r line; do notify-send "$line" done & PID2=$! gracefulexit() { kill "$PID1" kill "$PID2" exit 0 } trap "gracefulexit" INT TERM wait "$PID1" wait "$PID2"
This is the most common way to handle subprocess in shell scripts. But it got one main big issue:
`$!` is the pid of the while loop only, not the `dbus-monitor`, not the both of them.
In consequence, killing those PID in the trap handler will leave alone the dbus-monitors subprocesses unmanaged.
So how to manage those? This is the clean way:
#!/bin/sh FIFO1="/tmp/fifo1" mkfifo "$FIFO1" dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" \ >> "$FIFO1" & PID1=$! while read -r line; do notify-send "$line" done < "$FIFO1" & PID2=$! FIFO2="/tmp/fifo2" mkfifo "$FIFO2" dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" \ >> "$FIFO2" & PID3=$! while read -r line; do notify-send "$line" done < "$FIFO2" & PID4=$! gracefulexit() { kill "$PID1" kill "$PID2" kill "$PID3" kill "$PID4" rm "$FIFO1" rm "$FIFO2" exit 0 } trap "gracefulexit" INT TERM EXIT wait "$PID1" wait "$PID2" wait "$PID3" wait "$PID4"
This use named pipe fifo files to separate both commands and to grab each pids.
Here is two tips to make it simple and clean:
#!/bin/sh daemon_pids_cache="$(mktemp)" start_daemon() { "$@" & printf "%s\n" "$!" >> "$daemon_pids_cache" } stop_daemons() { while read -r PID; do kill "$PID" done < "$daemon_pids_cache" rm "$daemon_pids_cache" } PIDS="" start_daemon dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Voice',type='signal',member='CallAdded'" | \ while read -r line; do notify-send "$line" done & PIDS="$PIDS $!" start_daemon dbus-monitor --system "interface='org.freedesktop.ModemManager1.Modem.Messaging',type='signal',member='Added'" | \ while read -r line; do notify-send "$line" done & PIDS="$PIDS $!" gracefulexit() { stop_daemons for PID in $PIDS; do kill "$PID" done exit 0 } trap "gracefulexit" INT TERM EXIT for PID in $PIDS; do wait "$PID" done
If this post inspired you, feels free to leave a comment !