Related
I want to kill a whole process tree. What is the best way to do this using any common scripting languages? I am looking for a simple solution.
You don't say if the tree you want to kill is a single process group. (This is often the case if the tree is the result of forking from a server start or a shell command line.) You can discover process groups using GNU ps as follows:
ps x -o "%p %r %y %x %c "
If it is a process group you want to kill, just use the kill(1) command but instead of giving it a process number, give it the negation of the group number. For example to kill every process in group 5112, use kill -TERM -- -5112.
Kill all the processes belonging to the same process tree using the Process Group ID (PGID)
kill -- -$PGID Use default signal (TERM = 15)
kill -9 -$PGID Use the signal KILL (9)
You can retrieve the PGID from any Process-ID (PID) of the same process tree
kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*') (signal TERM)
kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*') (signal KILL)
Special thanks to tanager and Speakus for contributions on $PID remaining spaces and OSX compatibility.
Explanation
kill -9 -"$PGID" => Send signal 9 (KILL) to all child and grandchild...
PGID=$(ps opgid= "$PID") => Retrieve the Process-Group-ID from any Process-ID of the tree, not only the Process-Parent-ID. A variation of ps opgid= $PID is ps -o pgid --no-headers $PID where pgid can be replaced by pgrp. But:
ps inserts leading spaces when PID is less than five digits and right aligned as noticed by tanager. You can use: PGID=$(ps opgid= "$PID" | tr -d ' ')
ps from OSX always print the header, therefore Speakus proposes: PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
grep -o [0-9]* prints successive digits only (does not print spaces or alphabetical headers).
Further command lines
PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID" # kill -15
kill -INT -"$PGID" # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID" # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID" # restart a stopped process (above signals do not kill it)
sleep 2 # wait terminate process (more time if required)
kill -KILL -"$PGID" # kill -9 if it does not intercept signals (or buggy)
Limitation
As noticed by davide and Hubert Kario, when kill is invoked by a process belonging to the same tree, kill risks to kill itself before terminating the whole tree killing.
Therefore, be sure to run the command using a process having a different Process-Group-ID.
Long story
> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"
> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"
> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"
Run the process tree in background using '&'
> ./run-many-processes.sh &
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)
> PID=$! # get the Parent Process ID
> PGID=$(ps opgid= "$PID") # get the Process Group ID
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 28969 Ss 33021 0:00 -bash
28349 28957 28957 28349 pts/3 28969 S 33021 0:00 \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3 28969 S 33021 0:00 | | \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3 28969 S 33021 0:00 | | | \_ sleep 9999
28958 28963 28957 28349 pts/3 28969 S 33021 0:00 | | \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3 28969 S 33021 0:00 | | \_ sleep 9999
28957 28959 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3 28969 S 33021 0:00 | | \_ sleep 9999
28959 28962 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3 28969 S 33021 0:00 | \_ sleep 9999
28349 28969 28969 28349 pts/3 28969 R+ 33021 0:00 \_ ps fj
The command pkill -P $PID does not kill the grandchild:
> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+ Done ./run-many-processes.sh
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 28987 Ss 33021 0:00 -bash
28349 28987 28987 28349 pts/3 28987 R+ 33021 0:00 \_ ps fj
1 28963 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28962 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28961 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28960 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
The command kill -- -$PGID kills all processes including the grandchild.
> kill -- -"$PGID" # default signal is TERM (kill -15)
> kill -CONT -"$PGID" # awake stopped processes
> kill -KILL -"$PGID" # kill -9 to be sure
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 29039 Ss 33021 0:00 -bash
28349 29039 29039 28349 pts/3 29039 R+ 33021 0:00 \_ ps fj
Conclusion
I notice in this example PID and PGID are equal (28957).
This is why I originally thought kill -- -$PID was enough. But in the case the process is spawn within a Makefile the Process ID is different from the Group ID.
I think kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) is the best simple trick to kill a whole process tree when called from a different Group ID (another process tree).
pkill -TERM -P 27888
This will kill all processes that have the parent process ID 27888.
Or more robust:
CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS
which schedule killing 33 second later and politely ask processes to terminate.
See this answer for terminating all descendants.
To kill a process tree recursively, use killtree():
#!/bin/bash
killtree() {
local _pid=$1
local _sig=${2:--TERM}
kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
killtree ${_child} ${_sig}
done
kill -${_sig} ${_pid}
}
if [ $# -eq 0 -o $# -gt 2 ]; then
echo "Usage: $(basename $0) <pid> [signal]"
exit 1
fi
killtree $#
rkill command from pslist package sends given signal (or SIGTERM by default) to specified process and all its descendants:
rkill [-SIG] pid/name...
I use a little bit modified version of a method described here:
https://stackoverflow.com/a/5311362/563175
So it looks like that:
kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`
where 24901 is parent's PID.
It looks pretty ugly but does it's job perfectly.
Modified version of zhigang's answer:
#!/usr/bin/env bash
set -eu
killtree() {
local pid
for pid; do
kill -stop $pid
local cpid
for cpid in $(pgrep -P $pid); do
killtree $cpid
done
kill $pid
kill -cont $pid
wait $pid 2>/dev/null || true
done
}
cpids() {
local pid=$1 options=${2:-} space=${3:-}
local cpid
for cpid in $(pgrep -P $pid); do
echo "$space$cpid"
if [[ "${options/a/}" != "$options" ]]; then
cpids $cpid "$options" "$space "
fi
done
}
while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
cpids $$ a
sleep 1
done
killtree $cpid
echo ---
cpids $$ a
brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.
for child in $(ps -o pid -ax --ppid $PPID) do ....... done
I can't comment (not enough reputation), so I am forced to add a new answer, even though this is not really an answer.
There is a slight problem with the otherwise very nice and thorough answer given by #olibre on Feb 28. The output of ps opgid= $PID will contain leading spaces for a PID shorter than five digits because ps is justifying the column (right align the numbers). Within the entire command line, this results in a negative sign, followed by space(s), followed by the group PID. Simple solution is to pipe ps to tr to remove spaces:
kill -- -$( ps opgid= $PID | tr -d ' ' )
if you know pass the pid of the parent process, here's a shell script that should work:
for child in $(ps -o pid,ppid -ax | \
awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
echo "Killing child process $child because ppid = $pid"
kill $child
done
To add to Norman Ramsey's answer, it may be worth looking at at setsid if you want to create a process group.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html
The setsid() function shall create a
new session, if the calling process is
not a process group leader. Upon
return the calling process shall be
the session leader of this new
session, shall be the process group
leader of a new process group, and
shall have no controlling terminal.
The process group ID of the calling
process shall be set equal to the
process ID of the calling process. The
calling process shall be the only
process in the new process group and
the only process in the new session.
Which I take to mean that you can create a group from the starting process. I used this in php in order to be able to kill a whole process tree after starting it.
This may be a bad idea. I'd be interested in comments.
Inspired by ysth’s comment
kill -- -PGID
instead of giving it a process number, give it the negation of the group
number. As usual with almost any command, if you want a normal argument that
starts with a - to not be interpreted as a switch, precede it with --
It's super easy to do this with python using psutil. Just install psutil with pip and then you have a full suite of process manipulation tools:
def killChildren(pid):
parent = psutil.Process(pid)
for child in parent.get_children(True):
if child.is_running():
child.terminate()
Based on zhigang's answer, this avoids self-killing:
init_killtree() {
local pid=$1 child
for child in $(pgrep -P $pid); do
init_killtree $child
done
[ $pid -ne $$ ] && kill -kill $pid
}
The following shell function is similar to many of the other answers, but it works both on Linux and BSD (OS X, etc) without external dependencies like pgrep:
killtree() {
local parent=$1 child
for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
killtree $child
done
kill $parent
}
If you want to kill a process by name:
killall -9 -g someprocessname
or
pgrep someprocessname | xargs pkill -9 -g
This is my version of killing all the child processes using bash script.
It does not use recursion and depends on pgrep command.
Use
killtree.sh PID SIGNAL
Contents of killtrees.sh
#!/bin/bash
PID=$1
if [ -z $PID ];
then
echo "No pid specified"
fi
PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`
while [ ! -z "$CHILD_LIST" ]
do
PPLIST="$PPLIST,$CHILD_LIST"
CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done
SIGNAL=$2
if [ -z $SIGNAL ]
then
SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }
Here is a variation of #zhigang's answer which does without AWK, relying only on Bash's native parsing possibilities:
function killtree {
kill -STOP "$1"
ps -e -o pid= -o ppid= | while read -r pid ppid
do
[[ $ppid = $1 ]] || continue
killtree "$pid" || true # Skip over failures
done
kill -CONT "$1"
kill -TERM "$1"
}
It seems to work fine on both Macs and Linux. In situations where you can't rely on being able to manage process groups -- like when writing scripts for testing a piece of software which must be built in multiple environments -- this tree-walking technique is definitely helpful.
Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:
# kill my group's subprocesses: killGroup
# kill also myself: killGroup -x
# kill another group's subprocesses: killGroup N
# kill that group all: killGroup -x N
# N: PID of the main process (= process group ID).
function killGroup () {
local prid mainpid
case $1 in
-x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
"") mainpid=$$ ;;
*) mainpid=$1 ;;
esac
prid=$(ps ax -o pid,pgid | grep $mainpid)
prid=${prid//$mainpid/}
kill -9 $prid 2>/dev/null
return
}
Cheers.
if you have pstree and perl on your system, you can try this:
perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'
It is probably better to kill the parent before the children; otherwise the parent may likely spawn new children again before he is killed himself. These will survive the killing.
My version of ps is different from that above; maybe too old, therefore the strange grepping...
To use a shell script instead of a shell function has many advantages...
However, it is basically zhigangs idea
#!/bin/bash
if test $# -lt 1 ; then
echo >&2 "usage: kiltree pid (sig)"
fi ;
_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
killtree ${_child} ${_sig}
done
ps -o pid= --ppid $PPID | xargs kill -9
The following has been tested on FreeBSD, Linux and MacOS X and only depends on pgrep and kill (the ps -o versions don't work under BSD). First argument is parent pid of which children have to be terminated. second argument is a boolean to determine whether the parent pid has to be terminated too.
KillChilds() {
local pid="${1}"
local self="${2:-false}"
if children="$(pgrep -P "$pid")"; then
for child in $children; do
KillChilds "$child" true
done
fi
if [ "$self" == true ]; then
kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
fi
}
KillChilds $$ > /dev/null 2>&1
This will send SIGTERM to any child / grandchild process within a shell script and if SIGTERM doesn't succeed, it will wait 10 seconds and then send kill.
Earlier answer:
The following also works but will kill the shell itself on BSD.
KillSubTree() {
local parent="${1}"
for child in $(ps -o pid=$parent); do
if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1
I develop the solution of zhigang, xyuri and solidsneck further:
#!/bin/bash
if test $# -lt 1 ; then
echo >&2 "usage: kiltree pid (sig)"
exit 1 ;
fi ;
_pid=$1
_sig=${2:-TERM}
# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;
function _killtree () {
local _children
local _child
local _success
if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ;
return 1 ;
fi ;
# this avoids that children are spawned or disappear.
kill -SIGSTOP $2 ;
_children=$(ps -o pid --no-headers --ppid $2) ;
_success=0
for _child in ${_children}; do
_killtree $1 ${_child} $3 ;
_success=$(($_success+$?)) ;
done ;
if test $_success -eq 0 ; then
kill -$3 $2
fi ;
# when a stopped process is killed, it will linger in the system until it is continued
kill -SIGCONT $2
test $_success -eq 0 ;
return $?
}
_killtree $$ $_pid $_sig
This version will avoid killing its ancestry - which causes a flood of child processes in the previous solutions.
Processes are properly stopped before the child list is determined, so that no new children are created or disappear.
After being killed, the stopped jobs have to be continued to disappear from the system.
Old question, I know, but all the responses seem to keep calling ps, which I didn't like.
This awk-based solution doesn't require recursion and only calls ps once.
awk 'BEGIN {
p=1390
while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
o=1
while (o==1) {
o=0
split(p, q, " ")
for (i in q) if (a[q[i]]!="") {
p=p""a[q[i]]
o=1
a[q[i]]=""
}
}
system("kill -TERM "p)
}'
Or on a single-line:
awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'
Basically the idea is that we build up an array (a) of parent:child entries, then loop around the array finding children for our matching parents, adding them to our parents list (p) as we go.
If you don't want to kill the top-level process, then doing
sub(/[0-9]*/, "", p)
just before the system() line would remove it from the kill set.
Bear in mind that there is a race condition here, but that's true (as far as I can see) of all of the solutions. It does what I needed because the script I needed it for doesn't create lots of short-lived children.
An exercise for the reader would be to make it a 2-pass loop: after the first pass, send SIGSTOP to all processes in the p list, then loop to run ps again and after the second pass send SIGTERM, then SIGCONT. If you don't care about nice endings then second-pass could just be SIGKILL, I suppose.
To kill the whole process group at once, just like ^C does:
PID="$(pgrep -f unique_command_line_part)"
if [[ -n "$PID" ]]
then
PGID="$(ps --no-headers -p $PID -o pgid)"
kill -SIGINT -- -${PGID// /}
fi
Each line is explained in this answer
If you know the pid of the thing you want to kill, you can usually go from the session id, and everything in the same session. I'd double check, but I used this for scripts starting rsyncs in loops that I want to die, and not start another (because of the loop) as it would if I'd just killall'd rsync.
kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))
If you don't know the pid you can still nest more
kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))
In sh the jobs command will list the background processes. In some cases it might be better to kill the newest process first, e.g. the older one created a shared socket. In those cases sort the PIDs in reverse order. Sometimes you want to wait moment for the jobs to write something on disk or stuff like that before they stop.
And don't kill if you don't have to!
for SIGNAL in TERM KILL; do
for CHILD in $(jobs -s|sort -r); do
kill -s $SIGNAL $CHILD
sleep $MOMENT
done
done
Killing child process in shell script:
Many time we need to kill child process which are hanged or block for some reason. eg. FTP connection issue.
There are two approaches,
1) To create separate new parent for each child which will monitor and kill child process once timeout reached.
Create test.sh as follows,
#!/bin/bash
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
(sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;
and watch processes which are having name as 'test' in other terminal using following command.
watch -n1 'ps x -o "%p %r %c" | grep "test" '
Above script will create 4 new child processes and their parents. Each child process will run for 10sec. But once timeout of 5sec reach, thier respective parent processes will kill those childs.
So child won't be able to complete execution(10sec).
Play around those timings(switch 10 and 5) to see another behaviour. In that case child will finish execution in 5sec before it reaches timeout of 10sec.
2) Let the current parent monitor and kill child process once timeout reached. This won't create separate parent to monitor each child. Also you can manage all child processes properly within same parent.
Create test.sh as follows,
#!/bin/bash
declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
CMD_TIME=15;
for CMD in ${CMDs[*]}; do
(echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
CPIDs[$!]="$RN";
sleep 1;
done
GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
declare -A TMP_CPIDs;
for PID in "${!CPIDs[#]}"; do
echo "Checking "${CPIDs[$PID]}"=>"$PID;
if ps -p $PID > /dev/null ; then
echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
TMP_CPIDs[$PID]=${CPIDs[$PID]};
else
echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
fi
done
if [ ${#TMP_CPIDs[#]} == 0 ]; then
echo "All commands completed.";
break;
else
unset CPIDs;
declare -A CPIDs;
for PID in "${!TMP_CPIDs[#]}"; do
CPIDs[$PID]=${TMP_CPIDs[$PID]};
done
unset TMP_CPIDs;
if [ $CNT -gt $CNT_TIME_OUT ]; then
echo ${CPIDs[#]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
kill -- -$GPID;
fi
fi
CNT=$((CNT+1));
echo "waiting since $b secs..";
sleep 1;
done
exit;
and watch processes which are having name as 'test' in other terminal using following command.
watch -n1 'ps x -o "%p %r %c" | grep "test" '
Above script will create 4 new child processes. We are storing pids of all child process and looping over them to check if they are finished their execution or still running.
Child process will execution till CMD_TIME time. But if CNT_TIME_OUT timeout reach , All children will get killed by parent process.
You can switch timing and play around with script to see behavior.
One drawback of this approach is , it is using group id for killing all child tree. But parent process itself belong to same group so it will also get killed.
You may need to assign other group id to parent process if you don’t want parent to be killed.
More details can be found here,
Killing child process in shell script
This script also work:
#/bin/sh
while true
do
echo "Enter parent process id [type quit for exit]"
read ppid
if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then
exit 0
fi
for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'`
do
echo killing $i
kill $i
done
done
I've written a small bash script to start a program every 3 seconds. This script is executed on startup and it saves its PID into a pidfile:
#!/bin/bash
echo $$ > /var/run/start_gps-read.pid
while [ true ] ; do
if [ "$1" == "stop" ] ;
then
echo "Stopping GPS read script ..."
sudo pkill -F /var/run/start_gps-read.pid
exit
fi
sudo /home/dh/gps_read.exe /dev/ttyACM0 /home/dh/gps_files/gpsMaus_1.xml
sleep 3
done
The problem is, I can't terminate the shell script by calling start_gps-read.sh stop. There it should read the pidfile and stop the inital process (from startup).
But when I call stop, the script still runs:
dh#Raspi_DataHarvest:~$ sudo /etc/init.d/start_gps-read.sh stop
Stopping GPS read script ...
dh#Raspi_DataHarvest:~$ ps aux | grep start
root 488 0.0 0.3 5080 2892 ? Ss 13:30 0:00 /bin/bash /etc/init.d/start_gps-read.sh start
dh 1125 0.0 0.2 4296 2016 pts/0 S+ 13:34 0:00 grep start
Note: The script is always executed as sudo.
Does anyone know how to stop my shell script?
The "stop" check needs to come before you overwrite the pid file, and certainly doesn't need to be inside the loop.
if [ "$1" = stop ]; then
echo "Stopping ..."
sudo pkill -F /var/run/start_gps-read.pid
exit
fi
echo "$$" > /var/run/start_gps-read.pid
while true; do
sudo /home/dh/gps_read.exe ...
sleep 3
done
bash: 4.3.42(1)-release (x86_64-pc-linux-gnu)
Executing the following script:
# This is myscript.sh
line=$(ps aux | grep [m]yscript) # A => returns two duplicates processes (why?)
echo "'$line'"
ps aux | grep [m]yscript # B => returns only one
Output:
'tom 31836 0.0 0.0 17656 3132 pts/25 S+ 10:33 0:00 bash myscript.sh
tom 31837 0.0 0.0 17660 1736 pts/25 S+ 10:33 0:00 bash myscript.sh'
tom 31836 0.0 0.0 17660 3428 pts/25 S+ 10:33 0:00 bash myscript.sh
Why does the inline executed ps-snippet (A) return two lines?
Summary
This creates a subshell and hence two processes are running:
line=$(ps aux | grep [m]yscript)
This does not create a subshell. So, myscript.sh has only one process running:
ps aux | grep [m]yscript
Demonstration
Let's modify the script slightly so that the process and subprocess PIDs are saved in the variable line:
$ cat myscript.sh
# This is myscript.sh
line=$(ps aux | grep [m]yscript; echo $$ $BASHPID)
echo "'$line'"
ps aux | grep [m]yscript
In a bash script, $$ is the PID of the script and is unchanged in subshells. By contrast, when a subshell is entered, bash updates $BASHPID with the PID of the subshell.
Here is the output:
$ bash myscript.sh
'john1024 30226 0.0 0.0 13280 2884 pts/22 S+ 18:50 0:00 bash myscript.sh
john1024 30227 0.0 0.0 13284 1824 pts/22 S+ 18:50 0:00 bash myscript.sh
30226 30227'
john1024 30226 0.0 0.0 13284 3196 pts/22 S+ 18:50 0:00 bash myscript.sh
In this case, 30226 is the PID on the main script and 30227 is the PID of the subshell running ps aux | grep [m]yscript.
a command substitution ($(...))
each segment of a pipeline[1]
cause Bash to create a subshell (a child process created by forking the current shell process), but then Bash optimizes away subshells if they result in a single call to an external utility.
(What I think is happening in the optimization scenario is that a subshell is actually created but then instantly replaced by the external utility's process, via something like exec. Do let me know if you know for sure.)
Applied to your example:
line=$(ps aux | grep [m]yscript) creates 3 child processes:
1 subshell - the fork of your script you see as an additional match returned by grep.
2 child processes (1 for each pipeline segment) - ps and grep; they take the place of the optimized-away subshells; their parent process is the 1 remaining subshell created by the command substitution.
ps aux | grep [m]yscript creates 2 child processes (1 for each pipeline segment):
ps and grep; they take the place of the optimized-away subshells; their parent process is the current shell.
For an overview of the scenarios in which a subshell is created in Bash, see this answer of mine, which, however, doesn't cover the optimizing-away scenarios.
[1] In Bash v4.2+ you can set option lastpipe (off by default) in order to make the last pipeline segment run in the current shell instead of a subshell; aside from a slight efficiency gain, this allows you to declare variables in the last segment that the current shell can see after the pipeline exits.
I came across a weird scenario that is stumping me.
I have a script that I launch in the background with an &
example:
root## some_script.sh &
After running it, I do a ps -ef | grep some_script and I see TWO processes running where the 2nd process keeps getting an different PID but it's Parent is the process that I started (like the parent process is spawning children that die off - but this was never written in the code).
example:
root## ps -ef | grep some_script.sh
root 4696 17882 0 13:30 pts/2 00:00:00 /bin/bash ./some_script.sh
root 4778 4696 0 13:30 pts/2 00:00:00 /bin/bash ./some_script.sh
root## ps -ef | grep some_script.sh
root 4696 17882 0 13:30 pts/2 00:00:00 /bin/bash ./some_script.sh
root 4989 4696 0 13:30 pts/2 00:00:00 /bin/bash ./some_script.sh
What gives here? It seems to be messing up the output and functionality of the script too and basically makes it a never ending process (when I have a defined start and stop in the script).
the script:
`
#! /bin/bash
# Set Global Variables
LOGDIR="/srv/script_logs"
OUTDIR="/srv/audits"
BUCKET_LS=$OUTDIR"/LSOUT_"$i"_"$(date +%d%b%Y)".TXT"
MYCMD1="aws s3api list-objects --bucket viddler-flvs"
MYCMD2="--starting-token"
MAX_ITEMS="--max-items 10000"
MYSTARTING_TOKEN='""'
rm tokenlog.txt flv_out.txt
while [[ $MYSTARTING_TOKEN != "null" ]]
do
# First - Get the token for the next batch
CMD_PRE="$MYCMD1 $MAX_ITEMS $MYCMD2 $MYSTARTING_TOKEN"
MYSTARTING_TOKEN=($($CMD_PRE | jq -r .NextToken))
echo $MYSTARTING_TOKEN >> tokenlog.txt
# Now - get the values of the files for the existing batch
# First - re-run the batch and get the file values we want
MYOUT2=$($CMD_PRE | (jq ".Contents[] | {Key, Size, LastModified,StorageClass }"))
echo $MYOUT2 | sed 's/[{},"]//g;s/ /\n/g;s/StorageClass://g;s/LastModified://g;s/Size://g;s/Key://g;s/^ *//g;s/ *$//g' >> flv_out.txt
#echo $STARTING_TOKEN
done
`
I guess you have
(
some shell instructions
)
inside of your .sh
This syntax executes commands in the new process (but command line would be the same).
I want to kill a whole process tree. What is the best way to do this using any common scripting languages? I am looking for a simple solution.
You don't say if the tree you want to kill is a single process group. (This is often the case if the tree is the result of forking from a server start or a shell command line.) You can discover process groups using GNU ps as follows:
ps x -o "%p %r %y %x %c "
If it is a process group you want to kill, just use the kill(1) command but instead of giving it a process number, give it the negation of the group number. For example to kill every process in group 5112, use kill -TERM -- -5112.
Kill all the processes belonging to the same process tree using the Process Group ID (PGID)
kill -- -$PGID Use default signal (TERM = 15)
kill -9 -$PGID Use the signal KILL (9)
You can retrieve the PGID from any Process-ID (PID) of the same process tree
kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*') (signal TERM)
kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*') (signal KILL)
Special thanks to tanager and Speakus for contributions on $PID remaining spaces and OSX compatibility.
Explanation
kill -9 -"$PGID" => Send signal 9 (KILL) to all child and grandchild...
PGID=$(ps opgid= "$PID") => Retrieve the Process-Group-ID from any Process-ID of the tree, not only the Process-Parent-ID. A variation of ps opgid= $PID is ps -o pgid --no-headers $PID where pgid can be replaced by pgrp. But:
ps inserts leading spaces when PID is less than five digits and right aligned as noticed by tanager. You can use: PGID=$(ps opgid= "$PID" | tr -d ' ')
ps from OSX always print the header, therefore Speakus proposes: PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
grep -o [0-9]* prints successive digits only (does not print spaces or alphabetical headers).
Further command lines
PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID" # kill -15
kill -INT -"$PGID" # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID" # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID" # restart a stopped process (above signals do not kill it)
sleep 2 # wait terminate process (more time if required)
kill -KILL -"$PGID" # kill -9 if it does not intercept signals (or buggy)
Limitation
As noticed by davide and Hubert Kario, when kill is invoked by a process belonging to the same tree, kill risks to kill itself before terminating the whole tree killing.
Therefore, be sure to run the command using a process having a different Process-Group-ID.
Long story
> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"
> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"
> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"
Run the process tree in background using '&'
> ./run-many-processes.sh &
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)
> PID=$! # get the Parent Process ID
> PGID=$(ps opgid= "$PID") # get the Process Group ID
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 28969 Ss 33021 0:00 -bash
28349 28957 28957 28349 pts/3 28969 S 33021 0:00 \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3 28969 S 33021 0:00 | | \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3 28969 S 33021 0:00 | | | \_ sleep 9999
28958 28963 28957 28349 pts/3 28969 S 33021 0:00 | | \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3 28969 S 33021 0:00 | | \_ sleep 9999
28957 28959 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3 28969 S 33021 0:00 | | \_ sleep 9999
28959 28962 28957 28349 pts/3 28969 S 33021 0:00 | \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3 28969 S 33021 0:00 | \_ sleep 9999
28349 28969 28969 28349 pts/3 28969 R+ 33021 0:00 \_ ps fj
The command pkill -P $PID does not kill the grandchild:
> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+ Done ./run-many-processes.sh
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 28987 Ss 33021 0:00 -bash
28349 28987 28987 28349 pts/3 28987 R+ 33021 0:00 \_ ps fj
1 28963 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28962 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28961 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
1 28960 28957 28349 pts/3 28987 S 33021 0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3 28987 S 33021 0:00 \_ sleep 9999
The command kill -- -$PGID kills all processes including the grandchild.
> kill -- -"$PGID" # default signal is TERM (kill -15)
> kill -CONT -"$PGID" # awake stopped processes
> kill -KILL -"$PGID" # kill -9 to be sure
> ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
28348 28349 28349 28349 pts/3 29039 Ss 33021 0:00 -bash
28349 29039 29039 28349 pts/3 29039 R+ 33021 0:00 \_ ps fj
Conclusion
I notice in this example PID and PGID are equal (28957).
This is why I originally thought kill -- -$PID was enough. But in the case the process is spawn within a Makefile the Process ID is different from the Group ID.
I think kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) is the best simple trick to kill a whole process tree when called from a different Group ID (another process tree).
pkill -TERM -P 27888
This will kill all processes that have the parent process ID 27888.
Or more robust:
CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS
which schedule killing 33 second later and politely ask processes to terminate.
See this answer for terminating all descendants.
To kill a process tree recursively, use killtree():
#!/bin/bash
killtree() {
local _pid=$1
local _sig=${2:--TERM}
kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
killtree ${_child} ${_sig}
done
kill -${_sig} ${_pid}
}
if [ $# -eq 0 -o $# -gt 2 ]; then
echo "Usage: $(basename $0) <pid> [signal]"
exit 1
fi
killtree $#
rkill command from pslist package sends given signal (or SIGTERM by default) to specified process and all its descendants:
rkill [-SIG] pid/name...
I use a little bit modified version of a method described here:
https://stackoverflow.com/a/5311362/563175
So it looks like that:
kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`
where 24901 is parent's PID.
It looks pretty ugly but does it's job perfectly.
Modified version of zhigang's answer:
#!/usr/bin/env bash
set -eu
killtree() {
local pid
for pid; do
kill -stop $pid
local cpid
for cpid in $(pgrep -P $pid); do
killtree $cpid
done
kill $pid
kill -cont $pid
wait $pid 2>/dev/null || true
done
}
cpids() {
local pid=$1 options=${2:-} space=${3:-}
local cpid
for cpid in $(pgrep -P $pid); do
echo "$space$cpid"
if [[ "${options/a/}" != "$options" ]]; then
cpids $cpid "$options" "$space "
fi
done
}
while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
cpids $$ a
sleep 1
done
killtree $cpid
echo ---
cpids $$ a
brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.
for child in $(ps -o pid -ax --ppid $PPID) do ....... done
I can't comment (not enough reputation), so I am forced to add a new answer, even though this is not really an answer.
There is a slight problem with the otherwise very nice and thorough answer given by #olibre on Feb 28. The output of ps opgid= $PID will contain leading spaces for a PID shorter than five digits because ps is justifying the column (right align the numbers). Within the entire command line, this results in a negative sign, followed by space(s), followed by the group PID. Simple solution is to pipe ps to tr to remove spaces:
kill -- -$( ps opgid= $PID | tr -d ' ' )
if you know pass the pid of the parent process, here's a shell script that should work:
for child in $(ps -o pid,ppid -ax | \
awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
echo "Killing child process $child because ppid = $pid"
kill $child
done
To add to Norman Ramsey's answer, it may be worth looking at at setsid if you want to create a process group.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html
The setsid() function shall create a
new session, if the calling process is
not a process group leader. Upon
return the calling process shall be
the session leader of this new
session, shall be the process group
leader of a new process group, and
shall have no controlling terminal.
The process group ID of the calling
process shall be set equal to the
process ID of the calling process. The
calling process shall be the only
process in the new process group and
the only process in the new session.
Which I take to mean that you can create a group from the starting process. I used this in php in order to be able to kill a whole process tree after starting it.
This may be a bad idea. I'd be interested in comments.
Inspired by ysth’s comment
kill -- -PGID
instead of giving it a process number, give it the negation of the group
number. As usual with almost any command, if you want a normal argument that
starts with a - to not be interpreted as a switch, precede it with --
It's super easy to do this with python using psutil. Just install psutil with pip and then you have a full suite of process manipulation tools:
def killChildren(pid):
parent = psutil.Process(pid)
for child in parent.get_children(True):
if child.is_running():
child.terminate()
Based on zhigang's answer, this avoids self-killing:
init_killtree() {
local pid=$1 child
for child in $(pgrep -P $pid); do
init_killtree $child
done
[ $pid -ne $$ ] && kill -kill $pid
}
The following shell function is similar to many of the other answers, but it works both on Linux and BSD (OS X, etc) without external dependencies like pgrep:
killtree() {
local parent=$1 child
for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
killtree $child
done
kill $parent
}
If you want to kill a process by name:
killall -9 -g someprocessname
or
pgrep someprocessname | xargs pkill -9 -g
This is my version of killing all the child processes using bash script.
It does not use recursion and depends on pgrep command.
Use
killtree.sh PID SIGNAL
Contents of killtrees.sh
#!/bin/bash
PID=$1
if [ -z $PID ];
then
echo "No pid specified"
fi
PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`
while [ ! -z "$CHILD_LIST" ]
do
PPLIST="$PPLIST,$CHILD_LIST"
CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done
SIGNAL=$2
if [ -z $SIGNAL ]
then
SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }
Here is a variation of #zhigang's answer which does without AWK, relying only on Bash's native parsing possibilities:
function killtree {
kill -STOP "$1"
ps -e -o pid= -o ppid= | while read -r pid ppid
do
[[ $ppid = $1 ]] || continue
killtree "$pid" || true # Skip over failures
done
kill -CONT "$1"
kill -TERM "$1"
}
It seems to work fine on both Macs and Linux. In situations where you can't rely on being able to manage process groups -- like when writing scripts for testing a piece of software which must be built in multiple environments -- this tree-walking technique is definitely helpful.
Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:
# kill my group's subprocesses: killGroup
# kill also myself: killGroup -x
# kill another group's subprocesses: killGroup N
# kill that group all: killGroup -x N
# N: PID of the main process (= process group ID).
function killGroup () {
local prid mainpid
case $1 in
-x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
"") mainpid=$$ ;;
*) mainpid=$1 ;;
esac
prid=$(ps ax -o pid,pgid | grep $mainpid)
prid=${prid//$mainpid/}
kill -9 $prid 2>/dev/null
return
}
Cheers.
if you have pstree and perl on your system, you can try this:
perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'
It is probably better to kill the parent before the children; otherwise the parent may likely spawn new children again before he is killed himself. These will survive the killing.
My version of ps is different from that above; maybe too old, therefore the strange grepping...
To use a shell script instead of a shell function has many advantages...
However, it is basically zhigangs idea
#!/bin/bash
if test $# -lt 1 ; then
echo >&2 "usage: kiltree pid (sig)"
fi ;
_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
killtree ${_child} ${_sig}
done
ps -o pid= --ppid $PPID | xargs kill -9
The following has been tested on FreeBSD, Linux and MacOS X and only depends on pgrep and kill (the ps -o versions don't work under BSD). First argument is parent pid of which children have to be terminated. second argument is a boolean to determine whether the parent pid has to be terminated too.
KillChilds() {
local pid="${1}"
local self="${2:-false}"
if children="$(pgrep -P "$pid")"; then
for child in $children; do
KillChilds "$child" true
done
fi
if [ "$self" == true ]; then
kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
fi
}
KillChilds $$ > /dev/null 2>&1
This will send SIGTERM to any child / grandchild process within a shell script and if SIGTERM doesn't succeed, it will wait 10 seconds and then send kill.
Earlier answer:
The following also works but will kill the shell itself on BSD.
KillSubTree() {
local parent="${1}"
for child in $(ps -o pid=$parent); do
if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1
I develop the solution of zhigang, xyuri and solidsneck further:
#!/bin/bash
if test $# -lt 1 ; then
echo >&2 "usage: kiltree pid (sig)"
exit 1 ;
fi ;
_pid=$1
_sig=${2:-TERM}
# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;
function _killtree () {
local _children
local _child
local _success
if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ;
return 1 ;
fi ;
# this avoids that children are spawned or disappear.
kill -SIGSTOP $2 ;
_children=$(ps -o pid --no-headers --ppid $2) ;
_success=0
for _child in ${_children}; do
_killtree $1 ${_child} $3 ;
_success=$(($_success+$?)) ;
done ;
if test $_success -eq 0 ; then
kill -$3 $2
fi ;
# when a stopped process is killed, it will linger in the system until it is continued
kill -SIGCONT $2
test $_success -eq 0 ;
return $?
}
_killtree $$ $_pid $_sig
This version will avoid killing its ancestry - which causes a flood of child processes in the previous solutions.
Processes are properly stopped before the child list is determined, so that no new children are created or disappear.
After being killed, the stopped jobs have to be continued to disappear from the system.
Old question, I know, but all the responses seem to keep calling ps, which I didn't like.
This awk-based solution doesn't require recursion and only calls ps once.
awk 'BEGIN {
p=1390
while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
o=1
while (o==1) {
o=0
split(p, q, " ")
for (i in q) if (a[q[i]]!="") {
p=p""a[q[i]]
o=1
a[q[i]]=""
}
}
system("kill -TERM "p)
}'
Or on a single-line:
awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'
Basically the idea is that we build up an array (a) of parent:child entries, then loop around the array finding children for our matching parents, adding them to our parents list (p) as we go.
If you don't want to kill the top-level process, then doing
sub(/[0-9]*/, "", p)
just before the system() line would remove it from the kill set.
Bear in mind that there is a race condition here, but that's true (as far as I can see) of all of the solutions. It does what I needed because the script I needed it for doesn't create lots of short-lived children.
An exercise for the reader would be to make it a 2-pass loop: after the first pass, send SIGSTOP to all processes in the p list, then loop to run ps again and after the second pass send SIGTERM, then SIGCONT. If you don't care about nice endings then second-pass could just be SIGKILL, I suppose.
To kill the whole process group at once, just like ^C does:
PID="$(pgrep -f unique_command_line_part)"
if [[ -n "$PID" ]]
then
PGID="$(ps --no-headers -p $PID -o pgid)"
kill -SIGINT -- -${PGID// /}
fi
Each line is explained in this answer
If you know the pid of the thing you want to kill, you can usually go from the session id, and everything in the same session. I'd double check, but I used this for scripts starting rsyncs in loops that I want to die, and not start another (because of the loop) as it would if I'd just killall'd rsync.
kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))
If you don't know the pid you can still nest more
kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))
In sh the jobs command will list the background processes. In some cases it might be better to kill the newest process first, e.g. the older one created a shared socket. In those cases sort the PIDs in reverse order. Sometimes you want to wait moment for the jobs to write something on disk or stuff like that before they stop.
And don't kill if you don't have to!
for SIGNAL in TERM KILL; do
for CHILD in $(jobs -s|sort -r); do
kill -s $SIGNAL $CHILD
sleep $MOMENT
done
done
Killing child process in shell script:
Many time we need to kill child process which are hanged or block for some reason. eg. FTP connection issue.
There are two approaches,
1) To create separate new parent for each child which will monitor and kill child process once timeout reached.
Create test.sh as follows,
#!/bin/bash
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
(sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;
and watch processes which are having name as 'test' in other terminal using following command.
watch -n1 'ps x -o "%p %r %c" | grep "test" '
Above script will create 4 new child processes and their parents. Each child process will run for 10sec. But once timeout of 5sec reach, thier respective parent processes will kill those childs.
So child won't be able to complete execution(10sec).
Play around those timings(switch 10 and 5) to see another behaviour. In that case child will finish execution in 5sec before it reaches timeout of 10sec.
2) Let the current parent monitor and kill child process once timeout reached. This won't create separate parent to monitor each child. Also you can manage all child processes properly within same parent.
Create test.sh as follows,
#!/bin/bash
declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
CMD_TIME=15;
for CMD in ${CMDs[*]}; do
(echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
CPIDs[$!]="$RN";
sleep 1;
done
GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
declare -A TMP_CPIDs;
for PID in "${!CPIDs[#]}"; do
echo "Checking "${CPIDs[$PID]}"=>"$PID;
if ps -p $PID > /dev/null ; then
echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
TMP_CPIDs[$PID]=${CPIDs[$PID]};
else
echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
fi
done
if [ ${#TMP_CPIDs[#]} == 0 ]; then
echo "All commands completed.";
break;
else
unset CPIDs;
declare -A CPIDs;
for PID in "${!TMP_CPIDs[#]}"; do
CPIDs[$PID]=${TMP_CPIDs[$PID]};
done
unset TMP_CPIDs;
if [ $CNT -gt $CNT_TIME_OUT ]; then
echo ${CPIDs[#]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
kill -- -$GPID;
fi
fi
CNT=$((CNT+1));
echo "waiting since $b secs..";
sleep 1;
done
exit;
and watch processes which are having name as 'test' in other terminal using following command.
watch -n1 'ps x -o "%p %r %c" | grep "test" '
Above script will create 4 new child processes. We are storing pids of all child process and looping over them to check if they are finished their execution or still running.
Child process will execution till CMD_TIME time. But if CNT_TIME_OUT timeout reach , All children will get killed by parent process.
You can switch timing and play around with script to see behavior.
One drawback of this approach is , it is using group id for killing all child tree. But parent process itself belong to same group so it will also get killed.
You may need to assign other group id to parent process if you don’t want parent to be killed.
More details can be found here,
Killing child process in shell script
This script also work:
#/bin/sh
while true
do
echo "Enter parent process id [type quit for exit]"
read ppid
if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then
exit 0
fi
for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'`
do
echo killing $i
kill $i
done
done