How to Make a Bash Script Self‑Terminate Cleanly with trap and kill
This article explains why background loops in Bash scripts can survive script termination, demonstrates the problem with concrete examples, and provides robust solutions using trap, killall/pkill, and kill 0 to ensure all child processes exit together.
Problem description
When a Bash script starts a background command (e.g., sleep 50 &) and the script ends or is interrupted (e.g., Ctrl+C), the background process may become a child of init / systemd instead of terminating with the script. This is especially noticeable when the background command is a loop built from Bash keywords such as while, for, until, if, or case.
Illustrative scripts
Simple script that launches a single background sleep and kills it via $!:
# cat test1.sh
#!/bin/bash
echo $BASHPID
sleep 50 &
kill $!When the background command is a loop, a second Bash process is created to provide the execution environment for the loop:
# cat test2.sh
#!/bin/bash
echo $BASHPID
while true; do
sleep 50
echo 1
done &
sleep 60Running pstree -p | grep test2.sh shows two Bash processes: the original script and a subshell that runs the while loop. Killing the parent script leaves the subshell alive, which then re‑parents to init.
Why Bash built‑ins create extra shells
Bash keywords ( while, for, until, if, case) are not external commands; they run inside the current Bash process. When they are placed in the background, Bash must fork a new Bash subprocess to host the loop and its children. If the backgrounded construct originates from a script, the script process itself becomes the parent of that helper Bash. Consequently, terminating the original script does not automatically terminate the helper Bash, which then becomes an orphan and is adopted by init.
Basic kill attempts
Using kill $! works for a single background command, but does not affect the helper Bash created for a background loop. Adding killall $(basename $0) before exit can kill both script processes, yet it fails when the script is invoked as bash test1.sh because the process name becomes bash instead of the script filename.
Robust solution with pkill
Replace killall with a pattern‑based pkill and also kill the explicit background job stored in $!:
# cat robust.sh
#!/bin/bash
# Terminate on common signals and on script errors
trap 'pkill -f $(basename $0); exit 1' SIGINT SIGTERM EXIT ERR
while true; do
sleep 1
echo "hello world!"
done &
# Additional foreground work
sleep 60This trap runs whenever the script receives SIGINT, SIGTERM, exits normally, or encounters an error, ensuring that both the helper Bash (matched by its filename) and the explicit background job are killed.
Group termination with kill 0
The kill manual states that a PID of 0 sends the signal to every process in the current process group. A trap that executes kill 0 therefore terminates all processes spawned by the script, regardless of how they were started:
# cat kill0.sh
#!/bin/bash
trap 'echo signal_handled:; kill 0' SIGINT SIGTERM
while true; do
sleep 5
echo "hello world! hello world!"
done &
sleep 60Summary of recommended patterns
For scripts that may be interrupted, install a trap handling SIGINT, SIGTERM, EXIT, and ERR.
Inside the trap, either:
Use pkill -f $(basename $0) to kill any helper Bash processes that carry the script name, and optionally kill $! to terminate the most recent background job.
Or use kill 0 to send a signal to the entire process group.
Prefer pkill -f when the script may be invoked as ./script.sh or bash script.sh, because the process name differs in the two cases.
When only a single background command is needed, storing its PID in a variable (e.g., pid=$!) and killing it explicitly is sufficient.
Key take‑aways
Backgrounding Bash built‑ins spawns a helper Bash subprocess. If the parent script exits without explicitly terminating that helper, the helper (and its children) become orphans under init. Using a comprehensive trap that either pkill -f $(basename $0) or kill 0 guarantees that all child processes are cleaned up, making the script truly self‑destructing.
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.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
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.
