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 $?