#!/usr/skunk/bin/gawk -f # @(#) aj.gawk 2.0 94/03/28 # 92/03/09 John DuBois (john@armory.com) # 92/06/29 Ignore children of sdd and mscreen. Now uses awk instead of egrep. # 93/10/17 Ignore children of telnetd & scoterm # 94/02/25 Added IGNOREPROCS and .ajrc file # 94/03/19 Added command line args. Deal with defunct procs. # 94/03/20 Added ability to run on specific users, and i and I options. # 94/03/21 filbo@armory.com: handle "old" processes ("Mar 20" instead of time) # 94/03/28 john@armory.com: Converted to gawk script (use gawk for systime). BEGIN { Name = "aj" Usage = "Usage: " Name \ " [-cdhiIx] [-a] [-p] [-P] [user ...]" ARGC = Opts(Name,Usage,"a:cdHiIp:P:hx",0,"~/.ajrc","ALTUSERS,COUNTDEFUNCT"\ ",DISPLAYDEFUNCT,HEADER,IDLETIME,IDLESINCE,IGNOREPROCS,PARENTS", 1) if ("h" in Options) { printf \ Name ": print all interesting jobs being run by a user.\n"\ Usage "\n"\ "%s prints all processes that the user is running that are attached to any\n"\ "terminal, with the exception of sdd, mscreen, telnetd, scoterm, and their\n"\ "children (these are generally shells, daemons, or other uninteresting\n"\ "processes). The behaviour can be configured with options, or by setting\n"\ "variables in the environment or in a .ajrc file in one's home directory.\n"\ "PARENTS can be set to an egrep-style regular expression to replace the\n"\ "list of processes that should be ignored along with their children.\n"\ "IGNOREPROCS can be set to an egrep-style regular expression to match extra\n"\ "process names to ignore without ignoring their children.\n"\ "%s identifies as owned by the user any processes whose owner is the same\n"\ "as the user id given by USER, or if it not set, the name given by logname.\n"\ "ALTUSERS can be set to a list of users (separated by spaces or commas) to\n"\ "be reported on in addition to the user running %s. If any user names are\n"\ "given on the command line other than with the -a option, %s reports on\n"\ "them *instead of* the user running %s and the users given as ALTUSERS.\n"\ "Options:\n"\ "-a: Set the alternate users list.\n"\ "-c: Print a count of defunct processes. In .ajrc, set COUNTDEFUNCT.\n"\ "-h: Print this help.\n"\ "-H: Print a header. In .ajrc, set HEADER.\n"\ "-d: Show defunct processes. In .ajrc, set DISPLAYDEFUNCT.\n"\ "-i, -I: Replace the process' start time with the time the user has been\n"\ " idle and the time the user went idle, respectively. In .ajrc, set\n"\ " IDLETIME or IDLESINCE. The difference between -i and -I is that\n"\ " if e.g. nothing has been read from the user for 5 minutes, and it is\n"\ " now 10:33:16, -i would print 00:05:00 while -I would print 10:28:16.\n"\ " In both cases, the idle time is determined by the last time any input\n"\ " was read from the process' controlling tty (not a perfect indicator).\n"\ "-p: Set the ignored processes list.\n"\ "-P: Set the ignored parents list.\n"\ "-x: Print debug info.\n",Name,Name,Name,Name,Name exit 0 } Debug = "x" in Options ShowIdleTime = "i" in Options ShowIdleSince = "I" in Options ShowIdle = ShowIdleTime || ShowIdleSince DisplayDefunct = "d" in Options CountDefunct = "c" in Options IgnoreProcs = Options["p"] AltUsers = Options["a"] if ("P" in Options) ParentPat = Options["P"] else ParentPat = "sdd|mscreen|telnetd|scoterm" if (ARGC > 1) { Users = ARGV[1] for (i = 2; i < ARGC; i++) Users = Users "," ARGV[i] } else { if ("USER" in ENVIRON) Users = ENVIRON["USER"] else { "logname" | getline close("logname") Users = $0 } gsub(/ /,",",AltUsers) Users = Users "," AltUsers gsub(",+",",",Users) gsub("^,|,$","",Users) } NUser = split(Users,Junk,",") Cmd = "exec /bin/ksh -c 'echo $$; echo $PPID; exec ps -f" if (NUser < 20) Cmd = Cmd "u " Users else Cmd = Cmd "e" Cmd = Cmd "'" gsub(",","|",Users) UserPat = "^(" Users ")$" if (Debug) { printf "Debug=%s\nIgnoreProcs=%s\nParentPat=%s\nShowIdleTime=%s\n", Debug,IgnoreProcs,ParentPat,ShowIdleTime printf "ShowIdleSince=%s\nDisplayDefunct=%s\nCountDefunct=%s\n", ShowIdleSince,DisplayDefunct,CountDefunct printf "AltUsers=%s\nNUser=%s\nUserPat=%s\nCmd=%s\n", AltUsers,NUser,UserPat,Cmd } Cmd | getline psPID Cmd | getline PID Cmd | getline Header IgnorePIDs[psPID] # Ignore the ps IgnorePIDs[PID] # Ignore this program IgnorePPIDs[PID] # Ignore the shell that ran this program IgnorePPIDs[1] # Ignore children of init while ((Cmd | getline) == 1) { if ($1 ~ UserPat) ProcLine() } PrintResults("H" in Options) } function ProcLine() { date_adj = ($5 !~ ":") procname = $(8 + date_adj) sub(".*/","",procname) if (procname ~ ParentPat) { IgnorePPIDs[$2] return } # skip processes not attached to tty, IgnoreProcs, IgnorePIDs if ( !( ($(6 + date_adj) == "\?") || ($2 in IgnorePIDs) || IgnoreProcs != "" && (procname ~ IgnoreProcs)) ) { if ($NF == "") { NumDefunct++ if (DisplayDefunct == 1) RecordProc() } else RecordProc() } } function RecordProc() { if (Debug) print "Match: " $0 Procs[$2] = $0 Parents[$2] = $3 if ($5 ~ /^[A-Z]/) # handle "old" processes tty = $7 else tty = $6 ttys[$2] = "tty" tty ttylist = ttylist " tty" tty } function max(a,b) { if (a > b) return a else return b } function sec2hms(Seconds, Hours,Minutes) { Hours = int(Seconds / 3600) Seconds %= 3600 Minutes = int(Seconds / 60) Seconds %= 60 return sprintf("%02d:%02d:%02d",Hours,Minutes,Seconds) } function PrintResults(PrintHeader) { if (ShowIdle) { if (ShowIdleTime == 1) { Cmd = "cd /dev; stat -c\" \" -nfna" ttylist CurTime = systime() } else Cmd = "cd /dev; stat -c\" \" -nfnA -t%T" ttylist while ((Cmd | getline) == 1) if (ShowIdleTime == 1) IdleTime[$1] = sec2hms(max(CurTime - $2,0)) else IdleTime[$1] = $2 close(Cmd) } if (CountDefunct) printf "%d defunct processes.\n",NumDefunct if (PrintHeader) print " USER PID PPID C TIME TTY CPUTIME COMMAND" for (proc in Procs) if (!(Parents[proc] in IgnorePPIDs)) { if (ShowIdle && ttys[proc] in IdleTime) print substr(Procs[proc],1,24) IdleTime[ttys[proc]] \ substr(Procs[proc],33) # handle "old" processes else print Procs[proc] } else if (Debug) printf "Ignored: %s\n",Procs[proc] } # @(#) ProcArgs 1.2 94/03/08 # 92/02/29 john h. dubois iii # 93/07/18 Added "#" arg type # 93/09/26 Don't count -h against MinArgs # 94/01/01 Stop scanning at first non-option arg. Added '>' option type. # Removed meaning of '+' or '-' by itself. # 94/03/08 Added & option and *()< option types. # optlist is a string which contains all of the possible command line options. # A character followed by certain characters indicates that the option takes # an argument, with type as follows: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # The only difference the type of argument makes is in the runtime argument # error checking that is done. # The & option is a special case used to get numeric options without the # user having to give an option character. It is shorthand for [-+.0-9]. # If & is included in optlist and an option string that begins with one of # these characters is seen, the value given to "&" will include the first # char of the option. & must be followed by a type character other than ':'. # Note that if e.g. &> is given, an option of -.5 will produce an error. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" # or "+" is taken to be a non-option string; like other non-option strings, # it stops the scanning of argv and is left in argv[]. # If an option takes an argument, the argument may either immedately # follow it or be given separately. # If an option that does not take an argument is given, # an index with its name is created in options and its value is set to "1". # If an option that does take an argument is given, # an index with its name is created in options and its value # is set to the value of the argument given for it. # Options and their arguments are deleted from argv. # Note that this means that there may be gaps left in the indices of argv[]. # If compress is nonzero, argv[] is packed by moving its elements so that # they have contiguous integer indices starting with 0. # argv[0] is not examined. # An argument of "--" or "++" stops the scanning of argv[]. # The number of arguments left in argc is returned. # If an error occurs, the string OptErr is set to an error message and -1 is # returned. function ProcArgs(argc,argv,OptList,Options,compress, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue, NeedNextOpt) { # ArgNum is the index of the argument being processed. # ArgsLeft is the number of arguments left in argv. # Arg is the argument being processed. # ArgLen is the length of the argument being processed. # ArgInd is the position of the character in Arg being processed. # Option is the character in Arg being processed. # Pos is the position in OptList of the option being processed. # NumOpt is true if a numeric option may be given. ArgsLeft = argc NumOpt = index(OptList,"&") for (ArgNum = 1; ArgNum < argc; ArgNum++) { if ((Arg = argv[ArgNum]) !~ /^[-+]./) # Not an option; quit break delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) if (NumOpt && Option ~ /[-+.0-9]/) { Option = "&" Arg = "&" Arg ArgLen++ Pos = NumOpt } else if (!(Pos = index(OptList,Option)) || Option == "&") { OptErr = "Invalid option: -" Option return -1 } # Find what the option's value will be if it needs one if (NeedNextOpt = (ArgInd >= ArgLen)) # Value is the next arg Value = argv[ArgNum+1] else # Value is included with option Value = substr(Arg,ArgInd + 1) if (HadValue = AssignVal(Option,Value,Options, substr(OptList,Pos + 1,1),ArgNum < (argc - 1))) { if (HadValue == -1) return -1 if (NeedNextOpt) { delete argv[++ArgNum] ArgsLeft-- } break # Used up this option } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # Global variables: OptErr # Return value: -1 on error, 0 if option did not require an argument, # 1 if it did. function AssignVal(Option,Value,Options,ArgType,GotValue,Name, UsedValue,Err) { # If option takes a value... if (UsedValue = (ArgType ~ "[:*()#<>]")) { if (!GotValue) { if (Name != "") OptErr = "Variable requires a value -- " Name else OptErr = "option requires an argument -- " Option return -1 } if ((Err = CheckType(ArgType,Value,Option,Name)) != "") { OptErr = Err return -1 } } else Value = 1 if (!(Option in Options)) # Don't overwrite previously assigned values Options[Option] = Value return UsedValue } # Option is the option letter # Value is the value being assigned # Name is the var name of the option, if any # ArgType is one of: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # Returns null on success, err string on error function CheckType(ArgType,Value,Option,Name, Err) { if (ArgType == ":") return "" # A number begins with option + or -, and is followed by a string of # digits or a decimal with digits before it, after it, or both if (Value !~ /^[-+]?([0-9]+|[0-9]+?\.[0-9]+|[0-9]+\.)$/) Err = "must be a number" else if (ArgType ~ "[#<>]" && Value ~ /\./) Err = "may not include a fraction" else if (ArgType ~ "[()<>]" && Value < 0) Err = "may not be negative" else if (ArgType ~ "[)>]" && Value == 0) Err = "must be a positive number" if (Err != "") { if (Name != "") return "Value assigned to variable " Name " " Err else { if (Option == "&") Option = Value return "Value assigned to option -" Option " " Err } } else return "" } # Packs Arr to indices starting with 0 # Num should be the number of elements in Arr function PackArr(Arr,Num, NewInd,OldInd) { NewInd = OldInd = 0 for (; Num; Num--) { while (!(OldInd in Arr)) OldInd++ if (NewInd != OldInd) { Arr[NewInd] = Arr[OldInd] delete Arr[OldInd] } OldInd++ NewInd++ } } # Opts: Process command line arguments. # Opts processes command line arguments using ProcArgs() # and checks for errors. If an error occurs, a message is printed # and the program is exited. # # Input variables: # Name is the name of the program, for error messages. # Usage is a usage message, for error messages. # OptList the option description string, as used by ProcArgs(). # MinArgs is the minimum number of non-option arguments that this # program should have, non including ARGV[0] and +h. # If the program does not require any non-option arguments, # MinArgs should be omitted or given as 0. # Global variables: # The command line arguments are taken from ARGV[]. # The arguments that are option specifiers and values are removed from # ARGV[], leaving only ARGV[0] and the non-option arguments. # The number of elements in ARGV[] should be in ARGC. # After processing, ARGC is set to the number of elements left in ARGV[]. # The option values are put in Options[]. # On error, Err is set to 1 so it can be checked for in an END block. # Return value: The number of elements left in ARGV is returned. function Opts(Name,Usage,OptList,MinArgs,rcFile,VarNames,UseEnv, ArgsLeft) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList,Options,1) if ((ArgsLeft + ("h" in Options)) < (MinArgs+1)) { if (ArgsLeft != -1) OptErr = "Not enough arguments" print Name ": " OptErr ". Use -h for help." print Usage Err = 1 exit 1 } if (rcFile != "" && InitOpts(rcFile,Options,OptList,VarNames,UseEnv) == -1) { print Name ": " OptErr ". Use -h for help." Err = 1 exit 1 } return ArgsLeft } # Global vars: sets OptErr; uses Debug & ENVIRON[] function InitOpts(rcFile,Options,OptTypes,VarNames,UseEnv, Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret) { NumVars = split(VarNames,Vars,",") TypesInd = Ret = 0 for (i = 1; i <= NumVars; i++) { Var = Vars[i] CharOpt = substr(OptTypes,++TypesInd,1) if (CharOpt ~ "^[:*()#<>&]$") CharOpt = substr(OptTypes,++TypesInd,1) Map[Var] = CharOpt Types[Var] = Type = substr(OptTypes,TypesInd+1,1) # Don't overwrite entries from environment if (UseEnv && Var in ENVIRON && AssignVal(CharOpt,ENVIRON[Var],Options, Type,1,Var) == -1) return -1 } if (rcFile ~ "^~/") rcFile = ENVIRON["HOME"] substr(rcFile,2) while ((getline Line < rcFile) == 1) if (Line !~ /^#/ && Line !~ "^[ \t]*$") { if (Pos = index(Line,"=")) Var = substr(Line,1,Pos-1) else Var = Line # If no value, var is entire line if (Var in Map) { if (AssignVal(Map[Var],substr(Line,Pos+1),Options, Types[Var],Pos != 0,Var) == -1) return -1 } else { OptErr = sprintf("Unknown var \"%s\" set in %s",Var,rcFile) Ret = -1 } } if ("x" in Options) for (Var in Map) if (Map[Var] in Options) printf "%s=%s\n",Var,Options[Map[Var]] else printf "%s not set\n",Var return Ret }