Self‑Terminating Bash Scripts with trap, kill, and pkill
This article explains why background loops in Bash scripts can become orphaned under init/systemd, demonstrates how to reliably terminate such scripts by using trap handlers with kill, killall, pkill, and signal 0, and provides robust examples for various execution scenarios.
Script Self‑Termination in Bash
When a Bash script launches background tasks (e.g., sleep) and then exits, those tasks may become children of the init / systemd process instead of terminating with the script. This can leave orphaned processes that continue running after the script is stopped.
Simple termination works for a single background job:
#!/bin/bash
echo $BASHPID
sleep 50 &
kill $!However, if the background job is inside a loop ( while, for, until), killing $! is insufficient because the loop itself runs in a subshell that also needs to be terminated.
One approach is to use killall to kill all processes with the script’s name before exiting:
#!/bin/bash
trap 'killall `basename $0`' SIGINT
echo $BASHPID
while true; do
sleep 50
echo 1
done &
killall `basename $0`To handle both execution styles ( ./script.sh and bash script.sh) and to ensure the background subshell is also killed, replace killall with pkill -f and explicitly kill the job stored in $!:
#!/bin/bash
trap 'pkill -f `basename $0`' SIGINT
echo $BASHPID
while true; do
sleep 50
echo 1
done &
pid=$!
kill $pid
pkill -f `basename $0`A more robust solution combines all cleanup steps in a single trap that handles SIGINT, SIGTERM, EXIT, and ERR:
#!/bin/bash
trap 'pkill -f $(basename $0); exit 1' SIGINT SIGTERM EXIT ERR
while true; do
sleep 1
echo "hello world!"
done &
# additional foreground work
sleep 60For cases where you want to kill every process in the script’s process group, sending a signal to PID 0 works:
#!/bin/bash
trap 'echo "signal_handled:"; kill 0' SIGINT SIGTERM
while true; do
sleep 5
echo "hello world! hello world!"
done &
sleep 60Why does a background while create an extra Bash process? Bash built‑in commands ( while, for, until, etc.) run inside the current Bash process. When placed in the background, Bash spawns a new subshell to provide the required environment. If the script is the parent, that subshell becomes a child of the script; otherwise, an interactive shell creates a new Bash process. Killing the parent Bash process leaves the subshell (and its background jobs) re‑parented to init / systemd.
Examples of backgrounding built‑ins and observing the resulting process tree illustrate this behavior:
if true; then sleep 10; fi &
# or
while true; do sleep 2; done &After killing the original Bash, the newly created subshell remains attached to init, showing why proper cleanup via trap and group‑wide signals is essential for reliable script termination.
In summary, using trap to catch termination signals and employing kill, killall, pkill, or kill 0 ensures that all background jobs spawned by a Bash script are terminated, preventing orphaned processes from persisting under the system init.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Linux
Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
