#!/usr/skunk/bin/gawk -f #!/usr/bin/awk -f # @(#) pss.awk 1.0 94/03/10 # 93/05/28 john h. dubois iii (john@armory.com) # 93/09/30 Added help and -t options. # 94/03/10 Use gawk so - options can be given. # Should use FIELDWIDTHS for this, since that was the original idea... # 1 2 3 4 5 6 7 8 9 10 11 12 13 # F S UID PID PPID C PRI NI ADDR1 ADDR2 SZ WCHAN STIME # 14 15 16 # TTY TIME CMD # 20 S spcecdt 525 1 0 30 10 563 1089 68 f00fcd64 May 23 07 0:08 -ksh BEGIN { Name = "pss" SortField = "TIME" if (ProcArgs(ARGC,ARGV,"hnczs:",Options,1) == -1) { printf "%s: %s\n",Name,OptErr exit 1 } if ("h" in Options) { printf \ "%s: List processes sorted by any field.\n"\ "Options:\n"\ "-z: Sort by size field.\n"\ "-s : Sort by , where is one of the\n"\ " headers in the output of ps -fl: F, S, UID, PID, PPID, C, PRI, NI,\n"\ " ADDR1, ADDR2, SZ, WCHAN, STIME, TTY, TIME, and CMD. The default is\n"\ " TIME (CPU timeused by process).\n"\ "-n: Only processes with a non-zero sort field are printed. With the\n"\ " default sort field, this produces a listing of processes that have\n"\ " used one second or more of CPU time.\n"\ "-c: List cpu-using processes only.\n"\ " Equivalent to -ntC.\n",Name exit(0) } Cmd = "ps -efl; exit 0" # explicitely exit 0 so gawk won't complain Cmd | getline Header # Convince awk that Gutter is an array split("",Gutter) FWidths = SetFields(Header,0, "TTY=4 CMD=1000000000 COMMAND=100000000", Fields,Gutter) if ("z" in Options) SortField = "SZ" if ("s" in Options) SortField = toupper(Options["s"]) for (Elem in Fields) AllFields[Fields[Elem]] if (!(SortField in AllFields)) { printf "%s: %s: No such field.\n",Name,SortField > "/dev/stderr" exit 1 } Nonzero = "n" in Options if ("c" in Options) { Nonzero = 1 SortField = "C" } # F S UID PID PPID C PRI NI ADDR1 ADDR2 SZ WCHAN STIME TTY TIME CMD split("UID PID PPID C PRI NI SZ STIME TTY TIME CMD",Format) LineNum = 0 split(FWidths,Widths) while (Cmd | getline) { FieldsByName(Fields,FieldVals,$0,Widths) Lines[++LineNum] = MakeLine(Format,FieldVals,Gutter) SortInd[LineNum] = sprintf("%6s",FieldVals[SortField]) } qsort_arb_ind(SortInd,k) FieldsByName(Fields,FieldVals,Header,Widths) print MakeLine(Format,FieldVals,Gutter) for (i = LineNum; i >= 1; i--) { # Stop when a zero value is reached (one that has no non-0 digits in it) if (Nonzero && SortInd[k[i]] !~ "[1-9a-f]") exit print Lines[k[i]] } } function MakeLine(Format,Fields,Gutter, Line,Name) { for (i = 1; i in Format; i++) { Line = Line Fields[Name = Format[i]] if (Name in Gutter) Line = Line " " } return Line } # FieldsByName: Put fields in FieldVals[] indexed by their names as given in # FieldNames[]. # Line is the input line to split up. # Widths gives the width of each field. # FieldNames contains an index for each field name that is to be printed. function FieldsByName(FieldNames,FieldVals,Line,Widths, i,Pos) { Pos = 1 for (i = 1; i in Widths; i++) { if (i in FieldNames) FieldVals[FieldNames[i]] = substr(Line,Pos,Widths[i]) Pos += Widths[i] } } # SetFields: generate a FIELDWIDTHS string and a field name map from a header. # SetFields parses a header and uses it to generate a list of field widths. # Input vars: # Header is the header to parse. It should contain field names separted by # whitespace. # LeftAdj sets the default field name adjustment. # If 1, field names are processed as left adjusted by default. # This means that the columns corresponding to the spaces after a field # name are given to the field named on their left. # If LeftAdj is 0, field names are processed as right adjusted by default; # columns corresponding to the spaces after a field name are given to the # field named on their right. # AltAdjFields is a list of fields which have adjustment opposite the default. # It has the form "fieldname1=width fieldname2=width ..." # Each width specifies the width of the named field. # The width must be given so that the columns between a left-adjusted and a # right-adjusted field can be assigned. # Output vars: # Fields[] gives the names of the fields, indexed by the field numbers as # they will be set by gawk when FIELDWIDTHS is set. # Gutter[] is a list of those fields that should have a blank column added # after them. # Return value: # The return value is a FIELDWIDTHS string. # Example: # For parsing ps output in various formats: # FIELDWIDTHS = \ # SetFields(Header,0,"TTY=4 CMD=10000000 COMMAND=1000000",Fields,Gutter) function SetFields(Header,LeftAdj,AltAdjFields,Fields,Gutter, Offset,NFields,HFields,Pos,Alt,i,Name,Adj,Lengths,PreSpace,Columns, OtherField,FieldNum,Widths) { Offset = 1-LeftAdj*2 NFields = split(Header,HFields) Pos = 0 Assign(Alt,AltAdjFields," +","=") # Find width of name of each field, and number of spaces before each # Also set Adj[i] to whether field i is left-adjusted for (i = 1; i <= NFields; i++) { Name = HFields[i] if (Name in Alt) Adj[i] = !LeftAdj else Adj[i] = LeftAdj Header = substr(Header, (Lengths[i] = length(Name))+(PreSpace[i] = index(Header,Name) - 1) + 1) } for (i = 1; i <= NFields; i++) { # The columns that go to this field by default Columns = PreSpace[i+Adj[i]] # The field on the other side of the columns OtherField = i + Adj[i]*2-1 if (Adj[i] == Adj[OtherField]) # If this field and the other both have the same adjustment, # then this field gets the columns. Lengths[i] += Columns else { Name = HFields[i] # If this field and the other have different adjustment, then one # or the other other (but not both) of them is in Alt[]. # If it's this field, set its width to the value given in # Alt and give whatever is left to the other. if (Name in Alt) { Lengths[OtherField] += Lengths[i] + Columns - Alt[Name] Lengths[i] = Alt[Name] } } # If this field is right adjusted, and the other field is # left adjusted, and the other field follows this one, # add this field to the list of those that will need to have # a gutter added after it at print time. if (Adj[i] == 0 && Adj[i+1] == 1) { Name = HFields[i] Gutter[Name] } } FieldNum = 0 for (i = 1; i <= NFields; i++) { Name = HFields[i] Widths = Widths Lengths[i] " " Fields[++FieldNum] = Name # If this field is right adjusted and next is left adjusted, # there is an unused gap between them if (!Adj[i] && Adj[i+1]) { Widths = Widths PreSpace[i+1] " " ++FieldNum } } return Widths } # ProcArgs.awk john h. dubois iii 92/02/29 # optlist is a string which contains all of the possible command line options. # If a character is followed by a colon, # it indicates that that option takes an argument. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" or # "+" is not taken to be an option string (it is not acted on). # If an optoin 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) { # 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. ArgsLeft = argc for (ArgNum = 1; ArgNum < argc; ArgNum++) { Arg = argv[ArgNum] if (Arg ~ "^[-+]") { if ((Arg == "-") || (Arg == "+")) continue delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) Pos = index(optlist,Option) if (!Pos) { OptErr = "Invalid option: -" Option return -1 } if (substr(optlist,Pos + 1,1) == ":") { if (ArgInd < ArgLen) { options[Option] = substr(Arg,ArgInd + 1) ArgInd = ArgLen } else { if (ArgNum < (argc - 1)) { options[Option] = argv[++ArgNum] delete argv[ArgNum] ArgsLeft-- } else { OptErr = "Option -" Option " requires an argument." return -1 } } } else options[Option] = 1 } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # 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++ } } # Assign: make an array from a list of assignments. # An index with the name of each variable in the list is created in the array. # Its value is set to the value given for the # Input variables: # Elements is a string containing the list of variable-value pairs. # Sep is the string that separates the pairs in the list. # AssignOp is the string that separates variables from values. # Output variables: # Arr is the array. # Return value: the number of elements added to the set. # Example: # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=") function Assign(Arr,Elements,Sep,AssignOp, Num,Names,Elem,Assignments,Assignment) { Num = split(Elements,Assignments,Sep) for (; Num; Num--) { Assignment = Assignments[Num] Ind = index(Assignment,AssignOp) Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1) } return Num } # Arr is an array of values with arbitrary indices. # Array k is returned with numeric indices 1..n. # The values in k are the indices of array arr, # ordered so that if array arr is stepped through # in the order arr[k[1]] .. arr[k[n]], it will be stepped # through in order of the values of its elements. # The return value is the number of elements in the array (n). function qsort_arb_ind(arr,k, ArrInd,end) { end = 0 for (ArrInd in arr) k[++end] = ArrInd; qsortseg(arr,k,1,end); return end } function qsortseg(arr,k,start,end, left,right,sepval,tmp,tmpe,tmps) { # handle two-element case explicitely for a tiny speedup if ((end - start) == 1) { if (arr[tmps = k[start]] > arr[tmpe = k[end]]) { k[start] = tmpe k[end] = tmps } return } left = start; right = end; sepval = arr[k[int((left + right) / 2)]] # Make every element <= sepval be to the left of every element > sepval while (left < right) { while (arr[k[left]] < sepval) left++ while (arr[k[right]] > sepval) right-- if (left < right) { tmp = k[left] k[left++] = k[right] k[right--] = tmp } } if (left == right) if (arr[k[left]] < sepval) left++ else right-- if (start < right) qsortseg(arr,k,start,right) if (left < end) qsortseg(arr,k,left,end) }