BEGIN {
*gettimeofday = eval { require Time::HiRes } ?
\&Time::HiRes::gettimeofday :
sub { (time, 0) };
}
use Getopt::Long;
Getopt::Long::config qw(bundling no_getopt_compat);
my( $opt, @opt, %opt, @kill );
sub opt($$) { push @opt, "-$_[0]$_[1]" }
sub p_args { for( $ENV{"P_ARGS_$_[0]"} ) { defined or die "$0: \$P_ARGS_$_[0] not set\n"; push @ARGV, split } }
GetOptions \%opt,
qw'a b=s C d D=f e E f F I j k+ l L N P S T v w W x=s',
'i' => sub { push @opt, '-u', $< },
'K=s' => \@kill,
'o=s' => sub { push @opt, ($_[1] =~ /\bpid\b/) ? "-o$_[1]" : "-opid,$_[1]" },
'p=s' => \&opt,
'r' => sub { push @opt, '-uroot' },
't=s' => \&opt,
'u=s' => \&opt,
0 => sub { push @ARGV, '-fwdD0' },
1 => sub { push @ARGV, '-fwd' },
2 => sub { push @ARGV, '-fFw' },
map( ($_ => \&p_args), 3..9 ),
'help|?' => sub { print STDERR <<'EOF'; exit };
usage: p[ -aefIjlLrkdv -o<opt> -u<user> -p<pid> -t<tty> -K<sig> -D<seconds> -b<bsdopt>][ regexp ...]
-i show my processes
-r show root processes
-k kill, repeat suppresses question
-K kill with signal, can be repeated and -k suppresses question
-C sorted by CPU
-E sorted by executing command
-F sorted by father process, shown as a tree, can be combined with other sort
-I sorted by process ID
-N sorted by nice
-P sorted by prio
-S sorted by size
-T sorted by start time
-d diff (default: every second)
-D diff or loop time interval
-v vice versa, show processes not matching regexps
-b call bsd/ucb ps with bsd options
-w cut to $COLUMNS (default 132)
-x execute %p=pid (1 per command), %c=pid,pid..., %s=pid pid..., %P/%C/%S ppid, %%=%
-0 -fwdD0 (0secs diff loop)
-1 -fwd (1sec diff loop)
-2 -fFw (tree 2 width)
-3..-9 $P_ARGS_3..$P_ARGS_9
EOF
if( exists $opt{L} && $^O eq 'aix' ) {
delete $opt{L};
push @opt, '-moTHREAD';
}
push @opt, "-$opt"
if $opt = join '', grep exists $opt{$_}, qw'a e f j l L';
my $optD = exists $opt{D} ? $opt{D} : 1;
$opt{w} = $ENV{COLUMNS} || 132 and $ENV{COLUMNS} = $opt{w} + 4 # Gnu ps does less than COLUMNS :-(
if exists $opt{w};
my $re = 0;
if( @ARGV ) {
local $" = '|';
$re = qr/@ARGV/;
}
$odelta = 0, @otime = gettimeofday if $optD;
AGAIN:
# 5.8.0: my $ps = open PS, '-|', $cmd, @opt;
my $ps = open PS, '-|' or
exec $cmd, @opt;
AGAIN0:
my $found = 2;
if( @time ) {
<PS>;
} else {
$head = <PS>;
$delcol = index $head, ' C ';
# This is a heuristic for wide PIDs, on such a system ours is likely greater
$pidcol = index $head, $$ > 32767 ? ' PID' : ' PID';
$ppidcol = index $head, $$ > 32767 ? ' PPID' : ' PPID' if exists $opt{F} || exists $opt{x};
$head =~ s/ C //;
$cmdcol = index $head, 'CMD';
$cmdcol = index $head, 'COMMAND' if $cmdcol == -1;
$sortcol =
exists $opt{E} ? $cmdcol :
exists $opt{I} ? $pidcol :
index $head,
exists $opt{C} ? ($linux ? ' TIME' : ' TIME') :
exists $opt{T} ? ($linux ? 'STIME' : ' STIME') :
exists $opt{W} ? ($linux ? 'WCHAN' : ' WCHAN') :
exists $opt{S} ? ($linux ? 'DR SZ ' : ' SZ') :
exists $opt{P} ? ' PRI ' :
exists $opt{N} ? ' NI ' : '';
die "p: requested sort column not found\n" if $sortcol < 0;
}
@ps = grep {
$ok = 1;
s! inet(/\d+)!" in$1" . (' ' x (5 - length $1))!e;
if( $found ) {
$pid = int substr $_, $pidcol;
$found--, $ok = 0 if $pid == $$ || $pid == $ps;
}
$ok = exists $opt{v} ? ($_ !~ $re) : ($_ =~ $re)
if $re && $ok;
substr( $_, $delcol, 3 ) = '' if $ok && $delcol > 0;
unless( $linux ) {
# move time which overlaps into the command field forward
my $cor = $cmdcol - 1;
s/^(.{$cor}([:\d]+))/ my $s = $1; $cor = ' ' x length $2; $s =~ s!(.*)$cor!$1!; $s /e;
}
$ok;
} <PS>;
if( exists $opt{d} || defined $opt{D} ) {
close PS;
if( $optD < .2 ) { # on about 0 delay fire next subprocess asap
$ps = open PS, '-|' or
exec $cmd, @opt;
}
if( exists $opt{d} ) {
my %ps;
$ps{int substr $_, $pidcol} = $_
for @ps;
if( %oldps ) { # previous round output something
my $prevpid = -1;
@ps = ();
for( sort { $a <=> $b } keys %ps, keys %oldps ) {
next if $prevpid == $_;
if( $ps{$_} ) {
next if $ps{$_} eq $oldps{$_};
# Tack prefix onto end, to keep columns straight, fix it in output.
push @ps, $ps{$_} . ($oldps{$_} ? ' ' : '+ ');
} else {
push @ps, "$oldps{$_}- ";
}
$prevpid = $_;
}
} else {
$_ .= ' ' for @ps;
}
if( @ps ) {
if( @time ) {
printf '*** ' . substr( localtime $time[0], 4, 15 ) . ".%06d ***\n%s", $time[1], &output;
} else {
print ' ' . $head . &output;
}
}
%oldps = %ps;
} elsif( @ps ) { # found some
my $output = &output;
if( $output ne $lastoutput ) {
if( @time ) {
printf '*** ' . substr( localtime $time[0], 4, 15 ) . ".%06d ***\n%s", $time[1], $output;
} else {
print $head . $output;
}
$lastoutput = $output;
}
}
if( $optD ) {
$odelta += $optD; # Calculate delta against otime, so as to not cumulate imprecision.
@time = gettimeofday;
select undef, undef, undef, $odelta + $otime[0] - $time[0] + ($otime[1] - $time[1]) / 1_000_000;
@time = gettimeofday;
goto AGAIN;
} else {
@time = gettimeofday;
goto AGAIN0;
}
}
exit unless @ps;
# normal single shot operation
print $head . &output;
if( exists $opt{k} || @kill || exists $opt{x} ) {
my @pids = map { int substr $_, $pidcol } @ps;
if( exists $opt{x} ) {
for( $opt{x} ) {
my @ppids = map { int substr $_, $ppidcol } @ps if /%[CPS]/;
s/%%/\0/g;
s/%c/join ',', @pids/ge;
s/%C/join ',', @ppids/ge if @ppids;
s/%s/join ' ', @pids/ge;
s/%S/join ' ', @ppids/ge if @ppids;
if( /%p/i ) {
for( my $i = 0; $i < @pids; $i++ ) {
local $_ = $_;
s/%p/$pids[$i]/g;
s/%P/$ppids[$i]/g if @ppids;
tr/\0/%/;
print "$_\n";
system $_;
}
exit;
} else {
tr/\0/%/;
print "$_\n";
exec $_;
}
}
}
my $kill = ($opt{k} > 1 or exists $opt{k} && @kill);
unless( $kill ) {
local $| = 1;
$kill = join ' -', '', @kill;
print "kill$kill @pids? ";
$kill = (<STDIN> =~ /^[jy]/); # Esperanto and English
}
if( $kill ) {
@kill = 15 unless @kill;
kill $_ => @pids for @kill;
goto AGAIN;
}
}
__END__
=head1 ps wrapper
=over 4
=item *
adapts to various variants of ps (tested on Linux, Solaris, AIX, HP/UX &
Reliant Unix)
=item *
sorts by pid or other column you specify
=item *
allows starting a command on the selected processes, either command per pid,
or comma- or space-separated list
=item *
allows killing the selected processes with any signal
=item *
eliminates itself and ps process from output
=item *
options for all own (-i) or root's (-r) processes
=item *
allows grepping processes (optionally inversely) with Perl regexps
=item *
father mode (-F) for showing a process tree
=item *
eliminates C column, which is by definition useless
=item *
loop mode repeatedly outputs every n (fractional) seconds, specially
parallelized for 0 seconds to loop as fast as possible -- interesting when
grepping for running and/or runnable processes
=item *
loop mode with diff to previous output allows tracking processes as they appear
(+) and dissapear (-) or change in some dispayed parameter
=back
=begin CPAN
=head1 README
B<ps wrapper>
B<�� >sort by pid or other column
B<�� >skip self and ps
B<�� >grep (-v)
B<�� >tree
B<�� >can kill or call command
B<�� >loop mode (with diff)