killp

Signal (typically, terminate) specific processes according to their command line text. Many features - 'killp -?' tells you all the options, and see the documentation comments at the top of the script.

Latest update: 2015-10-09

#!/bin/bash

# Send signals to selected processes according to their command line text.

# Killp:
# - reports each running process whose command contains an egrep-style
#   match for the text on the killp command line
# - solicits confirmation from the user for sending a signal to each target
# - on each confirmation,  reports whether the signal was succesfully
#   delivered (the user, eg, might not be privileged to signal that process)
# - proceeds to the next qualifying process

# Many of these behaviors can be adjusted using command-line arguments.

# The user can designate the signal to be sent just as they would with
# the shell 'kill' command.  'TERM' is accordingly the default signal.

# Additionally, the user can restrict the candidate process to just
# those running under the users uid, with the '-m' option.

# killp can be run non-interactively.  It only lists the qualified
# candidates, delivering no signals, when the '-n' option is
# specified.  It delivers the designated signal to all qualified
# candidates if the '-y' option is specified.  (The '-n' option takes
# precedence.)

# A '--' command line option indicates that all following arguments
# are to be taken as the match pattern.  This is useful if the initial
# character of the match pattern is a '-'.

# Each prompt includes the sequence number of the candidate process,
# the total number of processes, the pid of the candidate, the command
# text as ps sees it, the pending signal, the set of valid responses,
# and then the default response, which will be sent on null input from
# the user.  The initial default response is no ('n').

# The prompting provides a number of options.  The valid responses are:
#
#      y - yes
#      n - no
#      Y - yes, and set default response to 'y'
#      N - no, and set default response to 'n'
#      g - 'go' - don't ask, just show rest of candidates and do default action
#      q - 'quit' - no more killing, exit immediately
#      ? - show this message
#
# The 'g' response allows the user to indicate that all the remaining
# candidates should be treated with the default response, which can
# first be reset to 'y' or 'n' using the 'Y' or 'N' responses.

# *Note for systems running SunOS 4.1.3*
#  ------------------------------------
# There's a Sun patch for ps which makes it work reliably with the
# combination of the 'xja' options.  Without it, ps will sporadically
# (but often) segmentatation faults, particularly when your processes
# are reaching higher pids.  The script accomodates this bug to some
# degree, but is less discriminating about the candidate processes it
# offers for kills.  You're better off installing Sun patch 100981-02,
# which consists of a repaired ps executable.

PATH=/bin:/usr/bin:/usr/ucb

  # Get the name of the script, in $scriptNm:
plainIFS="$IFS"; IFS="/$IFS"
for scriptNm in $0; do : ; done;
IFS=" "
IFS="$plainIFS"

  # The default signal:
sig="-TERM"

case "$(uname -s)" in
  Linux ) os=Linux
          awk=awk;;
  Darwin ) os=MacOSX
           awk=awk;;
  * ) case "$(uname -o)" in
        Cygwin ) os=Linux
                 awk=awk;;
        * ) os="$(uname -r)"
            awk=nawk;;
      esac;;
esac

UsageMsg () {
  echo \
"${scriptNm} [ -SIGNAL ] [ -m ] [ -n | -y ] [ -help | '-?' ] [ -- ] string ...
    For each process invoked containing egrep(1) match for 'string', ask
    user whether to bombard it with <SIGNAL> (default TERM).
      '-SIGNAL' names signal (default: \"-TERM\", 15) to send.
      '-m'  - attend only to user's (\"My\") processes.
      '-n'  - No signalling (or prompting) - just show number, pid,
	      and text of matching commands.
      '-p'  - show only Process ids, with no signally or prompting.
      '-y'  - Yes - send signal to matching procs, !no questions asked!
      '-help' and '-?'  -show this message and exit.
      '--'  - take all subsequent arguments as part of the command text.
    During interaction, use '?' to get help on response choices."
}

  # Parse the command line:
while [ "x$*" != "x" ]; do
  case "$1" in
    -m ) meOnly=t; shift;;
    -n ) alwaysNo=$1; shift;;
    -p ) pidOnly=t; shift;;
    -y ) alwaysYes=$1; shift;;
    -- ) shift; target="$*"; shift $#;;                 # Consume the rest
    -help | -\? ) UsageMsg; exit 0;;
    -[0-9] | -[1-9][0-9] | -[A-z]* )   sig=$1; shift;;
    * ) target="$*"; shift $#;;                         # Consume the rest
  esac
done

if [ x"$target" = "x" ]; then
  echo "${scriptNm}: nonsense - no command text specified.  Usage:" 1>&2
  UsageMsg
  exit 1
fi

EchoSansNL () {
  case $os in
    5* ) echo "$@"'\c';;
    * ) echo -n "$@";;
  esac
}

PsArgsLinux () {
    # Suitable ps args for Linux (at least, as of 1.1.59).
  psArgs="xjww"
  cmdCol=57
  pidFld=2
  pgidFld=3
  psOthersOpt=a
}
PsArgsMacOSX () {
    # Suitable ps args for Linux (at least, as of 1.1.59).
  PsArgsLinux
  pgidFld=""
}
PsArgsOS4 () {
    # Suitable ps args for SunOS4.
  PsArgsLinux
  cmdCol=54
  psArgs=-$psArgs
}
PsArgsOS5 () {
    # Suitable ps args for SunOS5 (solaris 2)
  psArgs=-fja
  cmdCol=60
  pidFld=2
  pgidFld=4
  psOthersOpt=e
}
AltPsArgsOS4 () {
    # Alternate ps args for SunOS4, for use when (SunOS 4.1.3) ps
    # 'xja' bug is screwing us up.
  psArgs=-xlw
  cmdCol=73
  pidFld=3
  pgidFld=""
  psOthersOpt=a
}

PsArgs () {
  vrsn="$1"
  if [ -z "$vrsn" ]; then vrsn="$os"; fi
  case $vrsn in
    4* ) PsArgsOS4;;
    5* ) PsArgsOS5;;
    Linux ) PsArgsLinux;;
    MacOSX ) PsArgsMacOSX;;
    alt4 ) AltPsArgsOS4;;
    * ) echo "Unimplemented OS '$1' - using linux configuration."
	PsArgsLinux;;
  esac
}

GetCandidates () {
    # Set var 'candidates' to the pid and command for all process
    # whose commands contain an 'nawk' (egrep) match for given string.
    # The process running this script and its offspring are excluded.
  target="$*"
  thisPid=$$

  PsArgs

  if [ "x$meOnly" = "x" ]; then psArgs=${psArgs}${psOthersOpt}; fi

  psLines="`ps $psArgs`"
  status=$?

  if [ $status != 0 ]; then
    echo "Somethings wrong with ps ($vrsn, ps $psArgs) - bailout."
    exit 1
  fi

  if [ -n "$pgidFld" ]; then
    useIdFld="$pgidFld"
  else
    useIdFld="$pidFld"
  fi

  # Filter out this process and offspring from the list:
  candidates=`IFS="";
              echo $psLines | \
              $awk 'NR != 1 \
		      { if (($matchIdFld != thisPid) &&
			    (substr($0,cmdCol) ~ target))
			    # All procs containing target string but, either,
			    # - not in the process group of this process, if
			    #   we have it,
			    # - or not having the pid, if we dont:
			  { got += 1
			    targets[got]=$pidFld " " substr($0,cmdCol)
			  }
		       }
                     END { print got
                           for (i=1; i<=got; i++) print targets[i] } ' \
                    thisPid=$thisPid target="$target" cmdCol=$cmdCol \
                    matchIdFld="$useIdFld" pidFld=$pidFld \
		    leadFldNm="$leadFldNm" -`

  return $status

}

Ask () {
	# Line split so $totalNum can be at bol, to discard leading spaces:
  EchoSansNL "$onNum/"
  EchoSansNL $totalNum, $pid" $command "		# Indicate target text.
  if [ x"$alwaysNo" != "x" ]; then
    # We're in no-action mode, just go on to the next one:
    echo ""
    return 1
  else
    # Set up for a kill:
    EchoSansNL " [$sig, y/n/g/q/?: $defResp] "
    if [ x"$alwaysYes" != "x" ]; then
      echo "[y]"
      return 0
    elif [ x"$alwaysDef" != "x" ]; then
      if [ "$defResp" = "y" ]; then
        echo "[y]"
        return 0
      else
        echo "[n]"
        return 1
      fi
    else
      read response <&4			# Get the response
    fi
      # Return code from GetAction will be returned.
    GetAction $response
    return $?
  fi
}

GetAction () {
  case $1 in
    y ) return 0;;
    Y ) defResp="y"; return 0;;
    n ) action=n; return 1;;
    N ) defResp="n"; return 1;;
    g ) alwaysDef=t; if [ "$defResp" = y ]; then return 0; else return 1; fi;;
    q ) exit 0;;
    ? ) echo "
      y - yes
      n - no
      g - 'go' - don't ask, just show rest of candidates, doing default action
      Y - yes, and set default response to 'y'
      N - no, and set default response to 'n'
      q - 'quit' - no more signalling or reporting - exit immediately
      ? - show this message
      "
        # Resolicit the response:
      Ask;;
    * ) case "$defResp" in
          y ) echo "y"; return 0;;
          * ) echo "n"; return 1;;
        esac;;
  esac
}

  # Dup stdin to file descr 4, so we can still read from stdin while
  # taking input (in the while loop) from a pipe:
exec 4<&0
  # ... and dup stderr to file descr 5 for showing errors to user
exec 5<&2

GetCandidates $target

status=$?

if [ $status != 0 ]; then
  echo "${scriptNm}: ps $psArgs returned error status $status - the kernel seems to be awry." 2>&1
fi

if [ x"$candidates" = "x" ]; then
  if [ x"$meOnly" != "x" ]; then
    qual="no $USER"
  else
    qual="no"
  fi
  echo "${scriptNm}: $qual commands matching '$target' found" 1>&2
  exit 1
fi

onNum=1
defResp=n

echo "$candidates" | \
  while read pid command; do
    # The first item is the count:
    if [ x"$totalNum" = "x" ]; then
      totalNum=$pid
    # The rest are the entries:
    else
      if [ -n "$pidOnly" ]; then
        echo $pid
      elif Ask; then
        kill $sig $pid
        if [ $? = 0 ]; then
          echo ... hit
        else
          echo ... missed
        fi
      fi
      onNum="`expr $onNum + 1`"			# Increment the target count.
    fi
  done

exit $?