#!/usr/bin/perl -w
# psgrep 

use strict;

# each field from the PS header
my @fieldnames = qw(FLAGS UID PID PPID PRI NICE SIZE 
	            RSS WCHAN STAT TTY TIME COMMAND);

# determine the unpack format needed (hardcoded for Linux ps)
my $fmt = cut2fmt(8, 14, 20, 26, 30, 34, 41, 47, 59, 63, 67, 72);

my %fields;			# where the data will sotre

die <<Thanatos unless @ARGV;
usage: $0 criterion ...
    Each criterion is one a Perl expression involving:
     @fieldnames
    All criteria must be met for a line to print.  
    Examples:
    Processes running with uid under 10:
	$0 'uid < 10'
	$0 'size > 10_000'
	$0 'size > 10_000' 'uid != 0'
	$0 'command =~ /x/i'
	$0 'command !~ /^-/'
	$0 'tty =~ /^[p-t]/' 

Thanatos

for my $name (@fieldnames) {
    no strict 'refs';
    *$name = *{lc $name} = sub () { $fields{$name} };
} 

open(PS, "ps wwaxl |") || die "cannot fork: $!";

my $code = "sub is_desirable { " . join(" and ", @ARGV) . " }";
unless (eval $code.1) {
    die "Error in code: $@\n\t$code\n";
}

print scalar <PS>;

while (<PS>) {
    @fields{@fieldnames} = trim(unpack($fmt, $_));
    print if is_desirable();
} 
close(PS) || die "ps failed!";

sub cut2fmt { 
    my(@positions) = @_;
    my $template  = '';
    my $lastpos   = 1;
    for my $place (@positions) {
        $template .= "A" . ($place - $lastpos) . " "; 
	$lastpos   = $place;
    }
    $template .= "A*";
    return $template;
}

sub trim {
    my @strings = @_;
    for (@strings) {
	s/^\s+//;
	s/\s+$//;
    }
    return wantarray ? @strings : $strings[0];
}