Make Bash Scripts Self‑Terminate: Using trap, kill, and pkill Effectively
This guide explains why background loops in Bash scripts can linger after the script ends, how they become orphaned under init/systemd, and provides robust techniques—including trap, kill, pkill, and signal 0—to ensure all child processes are terminated when the script exits.
Background: why background jobs survive script exit
When a Bash script starts a background command such as sleep 50 &, the child process is initially attached to the script's process. After the script terminates, the child is re‑parented to init / systemd (PID 1) and continues to run.
# test1.sh
#!/bin/bash
echo $BASHPID
sleep 50 &Simple kill approach and its limitation
For a single background command you can terminate it with kill $!, where $! holds the PID of the most recent background job. If the background job is a loop (e.g., while true; do sleep 50; echo 1; done &), Bash creates a subshell for the loop. Killing $! only stops the subshell; the sleep processes inside the loop remain alive under init.
# test2.sh
#!/bin/bash
echo $BASHPID
while true; do
sleep 50
echo 1
done &
sleep 60Robust self‑termination with trap
Install a trap handler for termination signals so that all background tasks are killed when the script ends (including when the user presses Ctrl+C). The handler can use pkill -f $(basename $0) or send a signal to the whole process group.
# robust.sh
#!/bin/bash
trap 'pkill -f $(basename $0); exit 1' SIGINT SIGTERM EXIT ERR
while true; do
sleep 1
echo "hello world!"
done &
# main work here
sleep 60This kills any process whose command line contains the script name, covering both ./script.sh and bash script.sh invocations.
Even tighter control with signal 0
Signal 0 does not terminate processes but checks whether a process group exists. Sending kill 0 from within the script targets every process in the current group, effectively killing all children of the script.
# kill0.sh
#!/bin/bash
trap 'echo "signal_handled:"; kill 0' SIGINT SIGTERM
while true; do
sleep 5
echo "hello world! hello world!"
done &
sleep 60Why Bash built‑ins behave this way
Keywords such as while, for, until, if, and case are Bash built‑ins. When executed in the foreground they run inside the current Bash process. When placed in the background Bash must create a new subshell (a separate Bash process) to provide an execution environment. Killing the original Bash process without also terminating the subshell leaves the subshell’s children (e.g., sleep) orphaned and adopted by init.
If the background built‑in is launched from an interactive shell, the parent is a newly created Bash subshell.
If it is launched from within a script, the parent is the script’s Bash process.
Therefore, using trap to kill the entire process group or invoking pkill -f eliminates the orphaned‑process problem.
Practical use case
Background loops are useful for tasks such as continuously syncing a file every second while another loop processes the data every minute. The techniques above ensure that such scripts can be stopped cleanly without leaving stray processes.
In summary, combining trap, appropriate signal handling, and process‑group‑aware commands ( kill 0 or pkill -f) provides a reliable way for Bash scripts to “self‑destruct” and avoid lingering background jobs.
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.
