#!/bin/ksh ##################################################################### # # Signal # # By: chux0r.org # # Yet another she BANG bin KraSHHHHH production # # PURPOSE: # The kill command is used to kill jobs using their PID and a signal # (SIGHUP, SIGKILL, etc). This is useful when one knows the PID. # But what if you wanted to signal all jobs belonging to user foobar? # What if you wanted to kill all programs by name? This usually # requires one to use the ps command to get a listing of those # processes in order to figure out their PIDs, ownership, what they # are named, etc. This script attempts to fill in this need. # # I heard Solaris had something called pprocess (sp?) that works # similarly. Too bad they did not create that for ksh. # # FUTURE MODS: # Because of the major differences between the way the BSD and GNU # versions of ps work, porting this to work with Linux's ps would # suck. Due to that reason and the fact that this shell script has # ballooned up considerably since I started writing, I plan to convert # this whole thing to C in the future. My original intent of writing # a bitchin ute in nothing but ksh has been fulfilled. Somebody owes # me $5 hehe ;) # # I want to implement the following features: # # 1. signal by PID (normal) -p --pid +done # 2. signal by command name (CMD) -c --cmd +done # 3. signal by STIME -t --time -fad # 4. signal by UID (number) -U --uid -held # 4a. signal by username -u --user +done # 5. signal children of PID (PPID) but not parent -P --childrenof -fad # 6. signal by TTY -T --tty -fad # 7. signal all using > specified pctge of the CPU -C --cpu -fad # 8. signal all using > specified pctge of avail.RAM -m --mem -fad # 9. combine 2 and 4a -o --cmdby -done # 9a. option 9 except leave newest proc alone -x --exceptnewest -done # 10. nice all procs over NI of (number) -n --nice -fad # 11. nice down all procs belonging to user (name) -N --niceuser -fad # 12. no user interaction req (for scripted use) -q --quiet +done # 13. print the version of signal -v --version +done # 14. print out a helpful message -h --help +done # NOTE +done == feature is done, error checking code done, alpha'd by Chuck # -done == feature is done, error checking code NOT in place, alpha'd by Chuck # -held == feature is not done, analysis revealed a problem # +uc == feature is under construction right now # -fad == feature has not been started yet (future addition). # Examples: # # To get some basic info about signal: # # signal -v # # To get some help with signal: # # signal --help # # To kill a process with a PID of 12345: # # signal -KILL -p 12345 # # To send SIGHUP to all httpd processes: # # signal 1 --cmd httpd # # To nuke all processes owned by username "pointyhairedboss": # # signal -9 -u pointyhairedboss # # To kill all commands named "vi" owned by user "pointyhairedboss": # # signal KILL --cmdby vi pointyhairedboss # # To send HUP to all processes named "mwm" owned by user "print01" except # the newest process: # # signal -1 -x mwm print01 # # To send SIGKILL to all processes owned by user "pointyhairedboss" with no prompting: # # signal KILL --user pointyhairedboss -q # # Yet another NOTE: # # Do not use this utility on ps, grep, or sed processes. Since I use these # heavily in processing the data, if one "signal'd" any of these, the # program would behave unpredictably. Mainly, this was created to be run as # "root" and to send signals to daemons, services, processes with children, # processes that spawn other processes, that sort of thing - ctg RELEASE="\nSignal, ver. 0.72 Alpha\n(copyleft) chux0r.org, 2001,2002,2003\n"; USAGE="Usage: signal -[type] -[c|o|p|u|x] [name] {name} {-q} OR signal [-v|-h]"; HELPFILE="signal.README" Q=0; ANSW="n"; if [ $# -lt 3 -a $1 != "-v" -a $1 != "--version" -a $1 != "-h" -a $1 != "--help" ]; then echo $USAGE; exit 1; fi if [ $# -eq 4 ]; then if [ $4 = "-q" ]; then Q=1 echo "(signal) Silent running 4 set" #test fi elif [ $# -eq 5 ]; then if [ $4 = $5 ]; then echo "(signal) Too many -q flags. Exiting"; exit 1;i elif [ $5 = "-q" ]; then Q=1 echo "(signal) Silent running 5 set" #test fi; fi case $1 in 1|-1|-HUP|HUP) SIGNO="-1"; echo "signal $SIGNO";; #echo test 2|-2|-INT|INT) SIGNO="-2"; echo "signal $SIGNO";; #echo test 3|-3|-QUIT|QUIT) SIGNO="-3"; echo "signal $SIGNO";; #echo test 6|-6|-ABRT|ABRT) SIGNO="-6"; echo "signal $SIGNO";; #echo test 9|-9|-KILL|KILL) SIGNO="-9"; echo "signal $SIGNO";; #echo test 15|-15|-TERM|TERM) SIGNO="-15"; echo "signal $SIGNO";; #echo test 17|-17|-STOP|STOP) SIGNO="-17"; echo "signal $SIGNO";; #echo test -v|--version) echo $RELEASE; exit 0;; -h|--help) SIGNALROOT=`which signal` if [ -f $SIGNALROOT".README" -o -r $SIGNALROOT".README" ];then more $SIGNALROOT".README" else echo "Can't open help file: $HELPFILE" echo "It's either not in the signal directory or it's not readable.\nExiting." fi; exit 0;; *) echo "\n$1 is not a valid signal type."; echo "The signal command allows the following signal types:"; echo "1(HUP), 2(INT), 3(QUIT), 6(ABRT), 9(KILL), 15(TERM), 17(STOP)."; echo "Examples: \$ signal -HUP ... or \$ signal -9 ...\n";exit 1;; esac echo $RELEASE case $2 in ## --- send signal to PID xxxxx --- ## --pid|-p) TARGET=$3; echo "target is: $TARGET"; if [ $TARGET = "" ]; then echo "You need to enter the PID to send the signal to."; echo $USAGE; exit 1; fi; SCOUNT=`ps -eo %p |grep -c $TARGET`; if [ $SCOUNT -gt 1 ]; then echo "Error. Multiple returns on PID $TARGET"; echo "Processes:\n" ps -eo "%U %p %c" |grep $TARGET|grep -v grep exit 1; elif [ $SCOUNT -lt 1 ]; then echo "Error. Negative number of processes returned (??)" echo "This is where I error out. Sorry. [EXITING]\n" exit 1; else kill $SIGNO $TARGET; fi; exit 0;; ## --- send signal to cmd or service named xxxxx --- ## --cmd|-c) TARGET=$3; echo "target is: $TARGET"; if [ $TARGET = "" ]; then echo "You need to enter the name of the command to send the signal to."; echo $USAGE; exit 1; fi; ## OBviously, if "grep" or "ps" is the target, this is gonna wig. -ctg SCOUNT=`ps -A |grep -c $TARGET`; if [ $SCOUNT -eq 0 ]; then echo "Error. Zero returns on \"$TARGET,\" PID not found."; exit; elif [ $SCOUNT -lt 0 ]; then echo "Negative number of processes returned for $TARGET on evaluation." echo "This is where I error out! Sorry. [EXITING] \n" exit 1; elif [ $SCOUNT -eq 1 ]; then if [ $Q -eq 0 ]; then printf "Signal \"$TARGET\" using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then PROC=`ps -eo "%p %c"|grep $TARGET|cut -c 1-5` PROC=`expr $PROC : '\([0-9]*\)'`; kill $SIGNO $PROC fi; elif [ $SCOUNT -gt 1 ]; then echo "Multiple PIDs returned named \"$TARGET\""; echo "Here they are:"; if [ $SCOUNT -gt 25 -a $Q -eq 0 ]; then ps -A| grep $TARGET |more; else ps -A| grep $TARGET; fi; if [ $Q -eq 0 ]; then printf "Signal all of them using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then ### if YES or QUIET set, stuff all PIDs into a ksh array ### set -A PROCARRAY `ps -A|grep $TARGET|cut -c 1-6`; i=`expr $SCOUNT - 1`; while [ $i -ge 0 ]; do ## ensure the target is integer, not string... PROCARRAY[$i]=`expr ${PROCARRAY[$i]} : '\([0-9]*\)'`; kill $SIGNO ${PROCARRAY[$i]}; i=`expr $i - 1`; done elif [ $ANSW != "y" -a $ANSW != "Y" -a $ANSW != "yes" ]; then echo "OK then, no signal(s) sent. Exiting."; exit 0; fi; fi; ### re-eval the processes ### clear; ## leave this out for now SCOUNT=`ps -A |grep -c $TARGET`; if [ $SCOUNT -lt 0 ]; then echo "Negative number of processes returned for $TARGET on reevaluation." echo "This is where I error out! Sorry. [EXITING] \n" exit 1; elif [ $SCOUNT -eq 0 ]; then echo "Process(es) named $3 obliterated. Killed. Dead, Jim." elif [ $SCOUNT -eq 1 ]; then echo "One Process named $3 still exists." ps -eo "%p %U %c"|grep $TARGET|grep -v grep; elif [ $SCOUNT -gt 1 ]; then echo "Multiple processes named $TARGET still exist" if [ $SCOUNT -gt 25 -a $Q -eq 0 ]; then ps -A| grep $TARGET | grep -v grep |more; else ps -A| grep $TARGET | grep -v grep; fi; fi; exit 0;; ## --- send signal to username xxx --- ## --user|-u) TARGET=$3; echo "Target is all procs owned by $TARGET" if [ $TARGET = "" ]; then echo "You need to enter the processss owner's username with this option."; echo $USAGE; exit 1; fi; MATCH=`grep -c $TARGET /etc/passwd` if [ $MATCH -eq 0 ]; then echo "Username \"$TARGET\" not found. Exiting." exit; fi; unset MATCH; if [ $TARGET = $USER -a $TARGET != "root" -a $Q -eq 0 ]; then echo "Hey, $TARGET is YOU." echo "Continuing will affect all of your own processes." echo "It also may kill your shell and/or have unpredictable results." printf "Abort? (y/n)"; ANSW=`line`; if [ $ANSW != "n" -a ANSW != "no" -a ANSW != "N" ]; then echo "Aborted. No signals sent." exit; fi; elif [ $TARGET = "root" -a $Q -eq 0 ]; then echo "Sending $1 to all root's processes can have unpredictable results." printf "Abort? (y/n)"; ANSW=`line`; if [ $ANSW != "n" -a ANSW != "no" -a ANSW != "N" ]; then echo "Aborted. No signals sent." exit; fi; fi; SCOUNT=`ps -eo "%U %p"|grep -c $TARGET` if [ $SCOUNT -eq 0 ]; then echo "Zero returns for \"$TARGET,\" No associated processes found.\n"; elif [ $SCOUNT -lt 0 ]; then echo "Error. Negative processes associated with user \"$TARGET\" (??)"; echo "That's just strange. Congrats, you found a bug! [EXITING]\n" exit 1; elif [ $SCOUNT -eq 1 ]; then echo "One process found: `ps -eo \"%U %c\" |grep $TARGET`" if [ $Q -eq 0 ]; then printf "Signal $TARGET\'s process using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then PROC=`ps -eo \"%p %U\"|grep $TARGET|cut -c 1-5` PROC=`expr $PROC : '\([0-9]*\)'`; kill $SIGNO $PROC elif [ $ANSW != "y" -a $ANSW != "Y" -a $ANSW != "yes" ]; then echo "Changed your mind, huh." echo "OK then, no signals sent." fi; elif [ $SCOUNT -gt 1 ]; then echo "Multiple processes found belonging to user \"$TARGET\"" echo "Here they are:" if [ $SCOUNT -gt 25 -a $Q -eq 0 ]; then ps -eo "%U %c"|grep $TARGET|grep -v grep|grep -v ps|more; else ps -eo "%U %c"|grep $TARGET|grep -v grep|grep -v ps; fi; if [ $Q -eq 0 ]; then printf "Do you want to signal all of them using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then set -A PROCARRAY `ps -eo "%p %U"|grep $TARGET|grep -v grep| grep -v ps| cut -c 1-5`; i=`expr $SCOUNT - 1`; while [ $i -ge 0 ]; do ## ensure the target is integer, not string... PROCARRAY[$i]=`expr ${PROCARRAY[$i]} : '\([0-9]*\)'`; kill $SIGNO ${PROCARRAY[$i]}; i=`expr $i - 1`; done elif [ $ANSW != "y" -a $ANSW != "Y" -a $ANSW != "yes" ]; then echo "Aborting. No signals sent." fi; fi; exit;; ## --- send signal to all commands x by username y --- ## --cmdby|-o) if [ $# -lt 4 ]; then echo $USAGE echo "(signal) The -o option (--cmdby) requires 2 parameters: command and username." exit 1 fi SCMD=$3; SUSR=$4; MATCH=`grep -c $SUSR /etc/passwd` if [ $MATCH -eq 0 ]; then echo "(signal) Username \"$SUSR\" not found. Exiting." exit; fi; SCOUNT=`ps -eo "%U %c" |grep $SCMD|grep -c $SUSR` if [ $SCOUNT -eq 0 ]; then echo "(signal) User \"$SUSR\" has no running processes. Exiting." exit; elif [ $SCOUNT -lt 0 ]; then echo "(signal) Error. Negative processes associated with user \"$SUSR\" (??)"; echo "That's just strange. Congrats, you found a bug! [EXITING]\n" exit 1; elif [ $SCOUNT -eq 1 ]; then echo "(signal)DEBUG_MSG:" echo "(signal) One process found: `ps -eo '%p %U %c'|grep $SCMD|grep $SUSR`" if [ $Q -eq 0 ]; then printf "Signal $SUSR\'s process \"SCMD\" using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then PROC=`ps -eo "%p %U %c"|grep $SCMD|grep $SUSR |cut -c 1-5` PROC=`expr $PROC : '\([0-9]*\)'`; kill $SIGNO $PROC elif [ $ANSW != "y" -a $ANSW != "Y" -a $ANSW != "yes" ]; then echo "Changed your mind, huh." echo "OK then, no signals sent." fi; elif [ $SCOUNT -gt 1 ]; then echo "(signal)Multiple processes named \"$SCMD\" found belonging to \"$SUSR\"" echo "Here they are:" if [ $SCOUNT -gt 25 -a $Q -eq 0 ]; then ps -eo "%U %c"|grep $SUSR|grep $SCMD|more; else ps -eo "%U %c"|grep $SUSR|grep $SCMD; fi; if [ $Q -eq 0 ]; then printf "Do you want to signal all of them using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then set -A PROCARRAY `ps -eo "%p %U %c"|grep $SCMD|grep $SUSR| cut -c 1-5`; i=`expr $SCOUNT - 1`; while [ $i -ge 0 ]; do ## ensure the target pid is integer, not string... PROCARRAY[$i]=`expr ${PROCARRAY[$i]} : '\([0-9]*\)'`; kill $SIGNO ${PROCARRAY[$i]}; i=`expr $i - 1`; done else echo "(signal)Aborting. No signals sent." fi; fi;; --exceptnewest|-x) if [ $# -lt 4 ]; then echo $USAGE echo "The -x option (--exceptnewest) requires 2 parameters: command and username." exit 1 fi SCMD=$3; SUSR=$4; MATCH=`grep -c $SUSR /etc/passwd` if [ $MATCH -eq 0 ]; then echo "Username \"$SUSR\" not found. Exiting." exit; fi; SCOUNT=`ps -eo "%U %c" |grep $SCMD|grep -c $SUSR` echo "scount at init == $SCOUNT" # test cmd if [ $SCOUNT -eq 0 ]; then echo "User \"$SUSR\" has no running processes. Exiting." exit; elif [ $SCOUNT -lt 0 ]; then echo "Error. Negative processes associated with user \"$SUSR\" (??)"; echo "That's just strange. Congrats, you found a bug! [EXITING]\n" exit 1; elif [ $SCOUNT -eq 1 ]; then echo "User \"$SUSR\" has only one process named \"$SCMD.\"" echo "Try using signal with the \"--cmdby\" option instead. Exiting." exit; elif [ $SCOUNT -ge 2 ]; then echo "Multiple processes named \"$SCMD\" found belonging to \"$SUSR\"" echo "Here they are:" if [ $SCOUNT -gt 25 -a $Q -eq 0 ]; then ps -eo "%U %c %t"|grep $SUSR|grep $SCMD|more; else ps -eo "%U %c %t"|grep $SUSR|grep $SCMD; fi; if [ $Q -eq 0 ]; then printf "Do you want to signal all except the most recent using $1? (y/n) "; ANSW=`line`; fi; if [ $ANSW = "y" -o $ANSW = "yes" -o $ANSW = "Y" -o $Q -eq 1 ]; then #This is more complex and takes more needling than the last option. #Since formatting of %t breaks when a command has been running #over 100 days (really broken after 1000 days), I decided to remove #all formatting delimiters and convert the number to an integer by #piping the output to sed. #This could very well be my longest pipeline ever :) -ctg set -A PIDTIMEARRAY `ps -eo "%p %U %c %t"|grep $SCMD|grep $SUSR|cut -c 1-5,24-38|sed "s/-//g"|sed "s/://g"` # !!!fix prob whereby grep matches "video" et al with str "vi"!!! set |grep PIDT; #test echo "scount == $SCOUNT"; j=`expr $SCOUNT \* 2 - 1`; #eval ETs now NEWEST=${PIDTIMEARRAY[$j]}; #start with this one... NPINDX=`expr $j - 1`; echo "j == $j" #test j=`expr $j - 2`; #decrement by 2 while [ $j -ge 1 ]; do echo "j == $j" #test if [ $NEWEST -gt ${PIDTIMEARRAY[$j]} ]; then NPINDX=`expr $j - 1`; #store the idx of the pid, not the ET! echo NPINDX is $NPINDX NEWEST=${PIDTIMEARRAY[$j]}; #store new lowest value elif [ $NEWEST -eq ${PIDTIMEARRAY[$j]} -a $Q -eq 0 ]; then echo "You have 2 processes that are exactly the same age" printf "Signal one of them arbitrarily? (y/n) "; ANSW=`line`; if [ ANSW != "y" -a ANSW != "Y" -a ANSW != "yes" ]; then EQNDX=`expr $j - 1`; echo "You might want to select which one you want to signal" echo "and use the \"kill\" command instead." echo "The PIDs with same elapsed time are: ${PIDTIMEARRAY[$NPINDX]} and ${PIDTIMEARRAY[$EQNDX]}\n Exiting." exit; fi; fi; j=`expr $j - 2`; done ##odd indeces in array have elapsed time, even indeces have PID i=`expr $SCOUNT \* 2 - 2`; #count lands on a pid index...not ET if [ $NPINDX -eq $i ]; then i=`expr $i - 2`; fi; while [ $i -ge 0 ]; do ## ensure the target pid is integer, not string... PIDTIMEARRAY[$i]=`expr ${PIDTIMEARRAY[$i]} : '\([0-9]*\)'`; kill $SIGNO ${PIDTIMEARRAY[$i]}; i=`expr $i - 2`; if [ $NPINDX -eq $i ]; then i=`expr $i - 2`; # skip over the newest one fi; done else echo "Aborting. No signals sent." fi; fi;; *) echo "$2 is not a valid flag";echo $USAGE;exit 1;; esac exit