add proper error handling for all final exec calls
[hband-tools.git] / user-tools / killp
blob67a57c8cce5c35384ff3f6949214bd47025b2b19
1 #!/bin/bash
3 true <<'EOF'
5 =pod
7 =head1 NAME
9 killp - Send signal to processes (kill, terminate, ...) by PID until they end
11 killpgrp - Send signal to processes (kill, terminate, ...) by PGID until they end
13 killcmd - Send signal to processes (kill, terminate, ...) by command line until they end
15 killexe - Send signal to processes (kill, terminate, ...) by executable path until they end
17 =head1 SYNOPSIS
19 killp [OPTIONS] <PID> [<PID> [...]]
21 =head1 DESCRIPTION
23 Send signal to process(es) by PID, PGID (process group ID), command name, or by executable path
24 until the selected process(es) exists.
25 Ie. in usuall invocation, eg. C<killcmd java> tries to SIGTERM all java processes until at
26 least 1 exists, and returns only afterwards.
28 =head1 OPTIONS
30 The following options control how B<killcmd> and B<killexe> finds processes.
31 Semantics are the same as in grep(1):
33 -E --extended-regexp
34 -F --fixed-strings
35 -G --basic-regexp
36 -P --perl-regexp
37 -i --ignore-case
38 -w --word-regexp
39 -x --line-regexp
41 Other options:
43 =over 4
45 =item -a
47 B<killcmd> looks for matching substring in the command's arguments too.
48 By default, only the command name is considered (first word in the command line).
50 =item -f
52 B<killcmd> and B<killexe> look for matching substring in the command's full path too.
53 By default, only the basename is considered.
55 =item [--]signal=I<SIG>, [-]s=I<SIG>
57 Which signal to send.
58 See kill(1) and signal(7) for valid I<SIG> signal names and numbers.
60 =item [--]interval=I<IVAL>
62 How much to wait between attempts.
63 See sleep(1) for valid I<IVAL> intervals.
65 =item -q, --quiet
67 =item -v, --verbose
69 By default, prints what is being killed on the second attempt onward.
70 With --verbose, prints the first attempt too.
71 With --quiet, does not print what is being killed.
73 =item -n, --dryrun
75 =back
77 =head1 SEE ALSO
79 kill(1), pkill(1), pgrep(1), killall(1), signal(7)
81 =cut
83 EOF
87 set -e
88 set -o pipefail
90 . /usr/lib/tool/bash-utils
94 case "${0##*/}" in
95 killp)
96 mode=pid
98 killpgrp)
99 mode=pgid
101 killcmd)
102 mode=cmd
104 killexe)
105 mode=exe
108 errx -2 "Call by either of these names: killp, killpgrp, killcmd, killexe"
110 esac
113 opt_verbose=''
114 opt_quiet=''
115 opt_dryrun=''
116 opt_match_command_args=''
117 opt_match_full_path=''
118 Signal=TERM
119 Interval=0.2
120 declare -A processes_left
121 declare -a grep_opts=()
122 declare -a args=()
124 # explode "-abc" arguments into separate "-a -b -c" pieces.
125 for arg in "$@"
127 case "$arg" in
128 -[!-]*)
129 pos=1
130 while [ -n "${arg:$pos:1}" ]
132 args+=("-${arg:$pos:1}")
133 let pos++
134 done
137 args+=("$arg")
139 esac
140 done
142 for arg in "${args[@]}"
144 case "$arg" in
145 signal=*|sig=*|s=*|--signal=*|--sig=*|-s=*)
146 Signal=${arg#*=}
148 interval=*|--interval=*)
149 Interval=${arg#*=}
151 --help)
152 pod2text "$0"
153 exit 0
155 -E|--extended-regexp|-F|--fixed-strings|-G|--basic-regexp|-P|--perl-regexp|-i|--ignore-case|-w|--word-regexp|-x|--line-regexp)
156 grep_opts+=("$arg")
159 if [ "$mode" != cmd ]
160 then
161 pod2text "$0" >&2
162 exit 2
164 opt_match_command_args=1
167 if [ "$mode" != cmd -a "$mode" != exe ]
168 then
169 pod2text "$0" >&2
170 exit 2
172 opt_match_full_path=1
174 -q|--quiet)
175 opt_quiet=1
177 -v|--verbose)
178 opt_verbose=1
180 -n|--dryrun|--dry-run)
181 opt_dryrun=1
183 -*|*=*)
184 pod2text "$0" >&2
185 exit 2
188 processes_left[$arg]=''
190 esac
191 done
194 find_processes()
196 # fill global $processes and $ps_title arrays
198 declare -g -a processes
200 case "$mode" in
201 cmd)
202 proclist=`ps -e o pid:1=,cmd= ww`
204 exe)
205 proclist=`rcmod any=0 find /proc -mindepth 2 -maxdepth 2 -name exe -printf '%h %l\n' 2>/dev/null | cut -b7-`
207 esac
209 proclist_to_search=$proclist
211 if [ "$mode" = cmd -a ! "$opt_match_command_args" ]
212 then
213 # discard the command arguments (keep the pid and the first word)
214 proclist_to_search=`cut -f 1,2 -d ' ' <<< "$proclist_to_search"`
216 if [ \( "$mode" = cmd -o "$mode" = exe \) -a ! "$opt_match_full_path" ]
217 then
218 # discard everything except pid (everything before space) and the basename (everything after the last slash)
219 proclist_to_search=`sed -e 's, .*/\([^/]\+\)$, \1,' <<< "$proclist_to_search"`
223 case "$mode" in
224 pid|pgid)
225 processes=("${!processes_left[@]}")
227 cmd|exe)
228 grep_params=()
229 for cmd in "${!processes_left[@]}"
231 grep_params+=(-e "$cmd")
232 done
233 lines=`echo "$proclist_to_search" | cut -d' ' -f2- | rcmod 1=0 grep --line-number "${grep_opts[@]}" "${grep_params[@]}" | cut -d: -f1`
235 declare -g -A ps_title=()
236 while read -r pid title
238 if [ $pid = $$ -o $pid = "$PPID" ]; then continue; fi # except ourself and parent
239 ps_title[$pid]=$title
240 done < <(echo "$proclist" | lines $lines)
242 processes=("${!ps_title[@]}")
244 esac
249 # don't use bash built-in kill command
250 enable -n kill
253 attempt=0
255 while [ ${#processes_left[@]} -gt 0 ]
257 if [ $attempt -gt 0 -a \( $mode = cmd -o $mode = exe \) ]
258 then
259 # leave some time for the processes to exit.
260 # it's usually faster to wait here a bit,
261 # than rush to find processes, then wait a longer time if the processes
262 # were still there which we killed.
263 sleep 0.1
266 find_processes
268 if [ ${#processes[@]} = 0 ]
269 then
270 # did not find any more of the named commands
271 if [ $attempt = 0 ]
272 then
273 warnx "no such process found"
275 break
279 if [ $attempt -gt 0 ]
280 then
281 sleep $Interval
283 find_processes
285 if [ ${#processes[@]} = 0 ]
286 then
287 # did not find more of the named commands
288 break
293 for proc in "${processes[@]}"
295 case "$mode" in
296 pid|cmd|exe)
297 id=$proc
298 what="process"
300 pgid)
301 id=-$proc
302 what="process group"
304 esac
306 if kill -- -0 "$id"
307 then
308 if [ ! $opt_quiet ]
309 then
310 if [ "$opt_verbose" -o $attempt -gt 0 ]
311 then
312 msg_end=''
313 case "$mode" in
314 cmd|exe)
315 msg_end=": ${ps_title[$proc]}"
317 esac
318 warnx "attempt $attempt: $id$msg_end"
322 if [ ! $opt_dryrun ]
323 then
324 kill -- -$Signal "$id" || true
326 else
327 case "$mode" in
328 pid|pgid)
329 warnx "$what $proc terminated"
330 unset processes_left[$proc]
332 cmd|exe)
333 warnx "$what $proc terminated: ${ps_title[$proc]}"
335 esac
337 done
340 attempt=$[attempt + 1]
341 done