Article 12017 of comp.lang.perl:
Newsgroups: comp.lang.perl
Path: feenix.metronet.com!news.utdallas.edu!wupost!howland.reston.ans.net!EU.net!sun4nl!ruuinf!henkp
From: henkp@cs.ruu.nl (Henk Penning)
Subject: Re: 'tail -f' & PERL
Message-ID: <CnA4Gu.HrD@cs.ruu.nl>
Keywords: syslog, tail
Sender: usenet@cs.ruu.nl (Six O'Clock News)
Organization: Utrecht University, Dept. of Computer Science
References: <2mn0d7$jod@decuk.uvo.dec.com>
Date: Sat, 26 Mar 1994 16:00:30 GMT
Lines: 806

In <2mn0d7$jod@decuk.uvo.dec.com> stuart@new.dec.com (Stuart Broderick) writes:

>I want to carry out monitoring of multiple files [...]

Below is a script+manpage which is basicly 'xtail in perl'.
It watches files (and directories) and reports changes.

An extra feature is the possibility to 'remember' the state
of a set of files, so you can inspect the changes that occurred
since the last time you looked.

Hope this helps			===  HenkP  ===

Henk P. Penning, Dept of Computer Science, Utrecht University \__/  \__/  \
Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. \__/  \__/
Telephone: +31-30-534106, fax: 513791, NIC-handle: HPP1 \__/  \__/  \__/  \
e-mail : henkp@cs.ruu.nl (uucp to sun4nl!ruuinf!henkp) _/  \__/  \__/  \__/

#!/bin/sh
# This is a shell archive (shar 3.32)
# made 03/26/1994 15:49 UTC by henkp@cs.ruu.nl
# Source directory /amd/alchemy/home/henkp/src/henkp/ptail
#
# existing files WILL be overwritten
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  16275 -rwxr-x--- ptail
#   4845 -rwx------ ptail.1
#
if touch 2>&1 | fgrep 'amc' > /dev/null
 then TOUCH=touch
 else TOUCH=true
fi
# ============= ptail ==============
echo "x - extracting ptail (Text)"
sed 's/^X//' << 'SHAR_EOF' > ptail &&
X#!/net/bin/perl
X
X$SIG_QUIT = 'QUIT' ;
X$SIG_INTR = 'INT' ;
X$MAGIC = 'ptail magic number 22' ;
X
X$opt_max  = 60 ;
X
X@STAT = ($DEV,$INO,$MODE,$NLINK,$UID,$GID,$RDEV,$SIZE,$ATIME,$MTIME,$CTIME,
X $BLKSIZE,$BLOCKS) = (0..12) ; 
X@TIME = ($SEC,$MIN,$HOUR,$MDAY,$MON,$YEAR,$WDAY,$YDAY,$ISDST) = (0..8) ;
X
X$globpat = '[*?[]' ;
X$__      = '------' ;
X$_x      = '******' ;
X$homedir = &homedir ;
X$replen  = 75 ;
X
X$prog = substr($0,rindex($0,'/')+1) ;
X$Usage = <<USAGE ;
XUsage: $prog [-v] [-max time] [-log log] [-f file] file|dir ...
XOr:    $prog -once -f file [file|dir ...]
XOr:    $prog -init -f file [file|dir ...]
Xoption v    : Verbose, report file-stat on every change.
Xoption max  : Maximum time [sec] between inspections (default $opt_max).
Xoption log  : Log changes in file 'log'.
Xoption f    : Init from and save status to file 'file'.
X              If 'file' starts with '=' (as in '=name') then assume
X	      'file' is '$homedir/.rc/$prog/name'.
Xoption once : Inspect and report only once.
Xoption init : Ignore previous state, initalize each object 'as-is-now'.
X-------------
Xarg file    : Show new data in file.
Xarg dir     : Show new data in files in dir.
X	      Treat dir as a 'glob-pattern' if dir matches /$globpat/
X	      yielding a list of files to watch.
X-------------
Xsignal INT  : Enter command interpreter.
Xsignal QUIT : Quit $prog, don't save status.
XUSAGE
X
Xsub Usage { $_[0] .= "\n" if length($_[0]) ; die "$_[0]$Usage" ; }
Xsub Error { die "$prog: $_[0]\n" ; }
Xsub Warn { warn "$prog: $_[0]\n" ; }
X
X# usage: &NGetOpt(ARG,ARG,ARG,..)
X# ARG = 'ID' | 'ID=SPC' | 'ID:SPC' for no-arg, required-arg or optional-arg
X# ID  = perl identifier
X# SPC = i|f|s for integer, fixedpoint real or string argument
X# defines $opt_ID as 1 or user specified value
X
Xrequire 'newgetopt.pl' ;
X&Usage() unless &NGetOpt('d','list','v','once','init',
X			 'max=i','log=s','f=s','help') ;
X&Usage() if $opt_help ;
X&Usage("Arg count") unless $opt_f || @ARGV ;
X
Xif ( $opt_log )
X  { open(LOG,">$opt_log") || &Error("Can't write LOG $opt_log ($!)") ;
X    local($old) = select LOG ; $| = 1 ;
X    select($old) ;
X  }
X
Xsub log { print @_ ; print(LOG @_) if $opt_log ; $modified = 1 ; }
X
X&Error("Option 'max' must not be negative ($opt_max)") if $opt_max < 0 ;
X
X$RC = &name_rc($opt_f) if defined $opt_f ;
X
X$SIG{$SIG_QUIT} = 'handle_QUIT' ;
X$SIG{$SIG_INTR} = 'handle_INTR' ;
X
Xsub cmp_dirs # return x if x in @old && x not_in @new
X  { local($_,%new) ;
X    grep($new{$_}++,splice(@_,0,shift)) ;
X    return sort grep(!$new{$_},@_) ;
X  }
X
Xsub stat
X  { local($obj,@res) = @_ ;
X    if ( $obj =~ /$globpat/o )
X      { return 'DIR', <${obj}> ; }
X    elsif ( -d $obj )
X      { opendir(DIR,$obj) || return('UNK') ;
X        @res = readdir(DIR) ;
X        closedir(DIR) ;
X	splice(@res,0,2) ;
X        return 'DIR', sort grep(s|^|$obj/|,@res) ;
X      }
X    elsif ( -f _ )
X      { return 'FIL', stat(_) ; }
X    else
X      { return 'UNK' ; }
X  }
X
Xsub inspect_obj
X  { local($obj) = @_ ;
X    local($same,$changed) = 1 ;
X    ($res,@res) = &stat($obj) ;
X    if ( $type{$obj} ne $res )
X      { # TYPE CHANGE
X        # DELETE THE OLD STUFF
X
X        $same = 0 ;
X
X        if ( $type{$obj} eq 'DIR' )
X          { &rep_stat("$__ DIR $obj gone\n") ;
X            for ( split($;,$stat{$obj}) )
X              { delete $fil{$_} ; }
X          }
X        elsif ( $type{$obj} eq 'FIL' )
X          { &rep_stat("$__ FIL $obj gone\n") ; }
X        elsif ( $type{$obj} ne 'UNK' )
X          { &Error("SYSERR: del <$obj> type <$type{$obj}>") ; }
X
X        # INIT THE NEW STUFF AS ON START-UP
X
X        $type{$obj} = $res ;
X        if ( $res eq 'DIR' )
X          { @res = grep((-f $_ ? $fil{$_} = join($;,stat(_)) : 0),@res) ;
X            &rep_stat("$__ type change: $obj is now a directory\n") ;
X          }
X        elsif ( $res eq 'FIL' )
X          { &rep_stat("$__ type change: $obj is now a file\n") ;
X            &rep_file("FIL",$obj,$res[$SIZE]) ;
X          }
X        elsif ( $res ne 'UNK' )
X          { &Error("SYSERR: type change <$obj> to <$res>") ; }
X        $stat{$obj} = join($;,@res) ;
X      }
X    else
X      { # DETERMINE if $obj changed state, and report
X
X        @stat = split($;,$stat{$obj}) ;
X        if ( $res eq 'FIL' )
X          { if ( $res[$SIZE] != $stat[$SIZE] )
X              { &rep_file("FIL",$obj,$res[$SIZE],$stat[$SIZE]) ; 
X                $same = 0 ;
X              }
X            $stat{$obj} = join($;,@res) ;
X          }
X        elsif ( $res eq 'DIR' )
X          { @del = &cmp_dirs(scalar(@res),@res,@stat) ;
X	    print "DIR $obj:\nres @res\nstat @stat\ndel @del\n" if $opt_d ;
X            $same = 0 if @del ;
X            for ( @del )
X              { # next unless defined $fil{$_} ;
X		&rep_stat("$__ fil $_: gone\n") ;
X                delete $fil{$_} ;
X              }
X            for ( @res )
X              { $changed = 1 ;
X                @nstat = stat($_) ;
X                unless ( @nstat )
X                  { &rep_stat("$_x fil $_: can't stat, assume gone\n") ;
X                    delete $fil{$_} ;
X                  }
X                elsif ( ! -f _ && defined $fil{$_} )
X                  { # was a file, but not now
X                    &rep_stat("$__ fil $_: not a file anymore\n") ;
X                    delete $fil{$_} ;
X                  }
X                elsif ( ! -f _ )
X                  { # not a file, ignore it
X		    &rep_stat("$__ fil $_: not a file, ignore\n") if $opt_d ;
X                    $changed = 0 ;
X                  }
X                elsif ( defined($fil{$_}) )
X                  { @ostat = split($;,$fil{$_}) ;
X                    if ( $nstat[$SIZE] != $ostat[$SIZE] )
X                      { &rep_file("fil",$_,$nstat[$SIZE],$ostat[$SIZE]) ; }
X                    else
X                      { $changed = 0 ; }
X                    $fil{$_} = join($;,@nstat) ;
X                  }
X                else
X                  { &rep_file("fil",$_,$nstat[$SIZE]) ;
X                    $fil{$_} = join($;,@nstat) ;
X                  }
X                $same &= ! $changed ;
X              }
X            $stat{$obj} = join($;,grep(defined($fil{$_}),@res)) ;
X          }
X        elsif ( $res ne 'UNK' )
X          { &Error("SYSERR: obj <$obj> type <$res>") ; }
X      }
X    return $same ;
X  }
X
Xsub inspect_objs
X  { for $obj ( @obj )
X      { if ( $time{$obj} > 0 )
X	  { # clock still ticking, assume unchanged
X	    $time{$obj}-- ;
X          }
X        else
X	  { if ( &inspect_obj($obj) )
X	      { $rate{$obj}++ if $rate{$obj} < $opt_max ; }
X	    else
X	      { $rate{$obj} = 0 ; }
X            $time{$obj} = $rate{$obj} ; ;
X	  }
X      }
X  }
X
Xsub init_obj
X  { local($obj) = @_ ;
X    ($res,@res) = &stat($obj) ;
X    $type{$obj} = $res ;
X    if ( $res eq 'DIR' )
X      { @res = grep( ( -f $_ ? $fil{$_} = join($;,stat(_)) : 0 ), @res) ; }
X    elsif ( $res ne 'FIL' && $res ne 'UNK' )
X      { &Error("SYSERR: init <$obj> type <$res>") ; }
X    $stat{$obj} = join($;,@res) ;
X  }
X
Xsub reset_rate_time
X  { for ( @obj )
X      { $rate{$_} = 0 ;
X        $time{$_} = 0 ;
X      }
X  }
X
Xsub init
X  { &read_rc($RC) if length($RC) ;
X    &set_state(keys %type) if $opt_init ;
X    for $ARG ( @ARGV )
X      { unless ( defined $type{$obj} )
X	  { &init_obj($ARG) ; }
X      }
X    @obj = sort keys %type ;
X    &Error("Nothing to watch !?") unless @obj ;
X    &reset_rate_time ;
X  }
X
Xsub doit
X  { if ( $interp )
X      { &interp ; }
X    else
X      { &inspect_objs ; }
X  }
X
Xsub dash
X  { local($x) = @_ ;
X    local($nl) ;
X    $nl = chop($x) if substr($x,-1,1) eq "\n" ;
X    if ( length($x) < $replen )
X      { $x .= ' ' ;
X	$x .= '-' x ($replen - length($x)) ;
X      }
X    return $x . $nl ;
X  }
X
Xsub rep_file
X  { local($pref,$file,$nsize,$osize) = @_ ;
X    local($incr,$chunk) = ($nsize-$osize,10240) ;
X    local($tmp,$from,$FH,$size,$rep) ;
X    if ( @_ == 3 ) # implying : not seen before
X      { $rep = sprintf("$__ $pref $file: size %d (new)\n", $nsize) ;
X	$from = 0 ;
X	$size = $nsize ;
X	$rep_file = undef ;
X      }
X    elsif ( @_ == 4 && $incr == 0 )
X      { &Error("SYSERR: incr0 <$file>") ; }
X    elsif ( $incr > 0 )
X      { $rep = sprintf("$__ $pref $file: size %d (%+d)\n", $nsize, $incr) ;
X        $from = $osize ;
X	$size = $incr ;
X      }
X    else # file shrunk ( $incr < 0 )
X      { $rep = sprintf("$__ $pref $file: size %d (%+d)\n", $nsize, $incr) ;
X	$from = 0 ;
X	$size = $nsize ;
X	$rep_file = undef ;
X      }
X
X    &log(&dash($rep)) if $opt_v || $file ne $rep_file ;
X    $rep_file = $file ;
X
X    return unless $FH = &open($file) ;
X
X    if ( $from > 0 )
X      { unless ( seek($FH,$from,0) )
X          { &log("$_x Can't seek $file to $from from 0\n") ;
X            &close($FH) ;
X            return ;
X          }
X      }
X
X    while ( $size > 0 )
X      { $chunk = ( $size < $chunk ) ? $size : $chunk ;
X	sysread($FH,$tmp,$chunk) ;
X        &log($tmp) ;
X	$size -= $chunk ;
X      }
X    &close($FH) ;
X  }
X
Xsub rep_stat
X  { local($rep) = @_ ;
X    &log(&dash($rep)) ;
X    $rep_file = undef ;
X  }
X
Xsub open  { return( open(NEW,$_[0]) ? 'NEW' : undef ) ; }
Xsub close { return( close(NEW) ? 'NEW' : undef ) ; }
X
Xsub handle_QUIT { exit ; }
Xsub handle_INTR
X  { $SIG{$SIG_INTR} = 'handle_INTR' ;
X    $interp = 1 ;
X  }
X
Xsub report
X  { local($type) = @_ ;
X    local($obj,$_) ;
X    $| = 1 ;
X    print "============ REPORT ============\n" ;
X    for $obj ( @obj )
X      { print "time $time{$obj}, rate $rate{$obj}, max $opt_max\n" ;
X        if ( $type{$obj} eq 'FIL' )
X          { printf "FIL %s %s\n", &mtime($stat{$obj}), $obj ; }
X        elsif ( $type{$obj} eq 'DIR' )
X          { print "DIR $obj\n" ;
X            if ( $type eq 'full' )
X	      { for ( split($;,$stat{$obj}) )
X                 { printf "  %s %s\n", &mtime($fil{$_}), $_ ; }
X	      }
X          }
X        elsif ( $type{$obj} eq 'UNK' )
X          { print "UNK $obj\n" ; }
X        else
X          { print "error: type $obj = <$type{$obj}>\n" ; }
X      }
X    print "default dump file: $RC\n" if length($RC) ;
X    printf "watching: DIRs %d, files %d, unknowns %d\n",
X      scalar(grep($_ eq 'DIR', values %type)),
X      scalar(grep(1, keys %fil)) + scalar(grep($_ eq 'FIL',values %type)),
X      scalar(grep($_ eq 'UNK', values %type)) ;
X    print "========== REPORT END ==========\n" ;
X  }
X
Xsub mtime
X  { &print_time
X      ( "%04d-%02d-%02d %02d:%02d:%02d",      # format
X        localtime((split($;,$_[0]))[$MTIME]), # time
X        $YEAR,$MON,$MDAY,$HOUR,$MIN,$SEC      # fields
X      ) ;
X  }
X
Xsub print_time
X  { local($format) = shift ;
X    local(@time) = splice(@_,0,scalar(@TIME)) ;
X    $time[$MON]++ ;
X    $time[$YEAR] += 1900 ;
X    sprintf($format,@time[@_]) ;
X  }
X
Xsub init_HELP_MENU {
X$HELP = <<HELP ;
X$prog HELP : only the first letter of the command is significant
Xc)ontinue
X  resume scanning mode
Xq)uit
X  leave $prog
X  dump status: $rcdef
Xx)it
X  exit $prog, don't attempt to dump status
Xr)eport
X  show last-changed-date (mtime) of all objects being watched
XR)eport
X  show last-changed-date (mtime) of all files being watched
Xi)nspect
X  inspect all objects, report changes
Xs)ave
X  save current status. Prog $prog asks for a filename,
X  default: $rcdef,
X  filename '=name' is interpreted as $rcdir
Xb)ackground
X  Suspend $prog.
X
XHELP
X$menu = <<MENU ;
X?=HELP, c)ontinue, q)uit, x)it, r)eport, i)nspect, s)ave, b)ackground
XMENU
X}
X
Xsub interp
X  { local($def,$entered) = ('c',time) ; ;
X    local($ans,$spent,$obj,$HELP,$same) ;
X    local($rcdef) = length($RC) ? $RC : "don't dump" ;
X    local($rcdir) = &name_rc('=name') ;
X    &init_HELP_MENU ;
X
X    while ( 1 )
X      { $rep_file = undef ;
X	print "$menu" ;
X	print "$prog [$def]> " ;
X        $full_ans = $ans = <STDIN> ;
X	unless ( length($ans) )
X	  { print "no input\n" ; next ; }
X	chop $ans ;
X	$ans = length($ans) ? substr($ans,0,1) : $def ;
X	if ( $ans =~ /^[hH?]$/ )
X	  { print $HELP ; }
X	elsif ( $ans eq 'c' )
X	  { $spent = time - $entered ;
X	    for $obj ( @obj )
X	      { $time{$obj} -= $spent ; }
X	    last ;
X	  }
X	elsif ( $ans eq 'q' )
X	  { &exit ; }
X	elsif ( $ans eq 'x' )
X	  { exit ; }
X	elsif ( $ans eq 'r' )
X	  { &report() ;
X	    $def = $ans ;
X	  }
X	elsif ( $ans eq 'R' )
X	  { &report('full') ;
X	    $def = $ans ;
X	  }
X	elsif ( $ans eq 'i' )
X	  { $same = 1 ;
X	    for $obj ( @obj )
X              { $same &= &inspect_obj($obj) ; }
X	    print "inspect: no change\n" if $same ;
X	    $def = $ans ;
X	  }
X	elsif ( $ans eq 's' )
X	  { printf "save on [%s]: ", length($RC) ? $RC : 'no default' ;
X            $ans = <STDIN> ;
X	    unless ( length($ans) )
X	      { print "no input: not saved\n" ; next ; }
X	    chop $ans ;
X	    $ans = $RC unless length($ans) ;
X	    unless ( length($ans) )
X	      { print "empty answer and no default: not saved\n" ; }
X	    else
X	      { $ans = &name_rc($ans) ;
X		if ( &dump_rc($ans) )
X		  { $rcdef = $RC = $ans ;
X		    &init_HELP_MENU ;
X		  }
X	      }
X	  }
X	elsif ( $ans eq 'b' )
X	  { kill('TSTP',0) ;
X	    $def = 'i' ;
X	  }
X	elsif ( $ans eq '!' )
X	  { $full_ans = substr($full_ans,1) ;
X	    system($full_ans) ;
X	  }
X	else
X	  { print "not on the menu ($ans)\n" ; }
X      }
X    $interp = 0 ;
X  }
X
Xsub main
X  { &init ; 
X    if ( $opt_list ) { &report('full') ; exit ; }
X    if ( $opt_once ) { &doit ; &exit ; }
X    while ( 1 )
X      { sleep 1 ;
X	&doit ;
X      }
X  }
X
X&main ;
X
Xsub homedir
X  { local($home) = $ENV{'HOME'} || (getpwuid($<))[7] ;
X    &Error("can't get homedir") unless $home ;
X    return $home ;
X  }
X
Xsub name_rc
X  { local($dump) = @_ ;
X    local($rc) ;
X    $dump =~ s/^\s+// ;
X    $dump =~ s/\s+$// ;
X    return '' unless length($dump) ;
X    unless ( $dump =~ /^=/ )
X      { $rc = $dump ; }
X    else
X      { $dump = substr($dump,1) ;
X        $rc = $homedir . "/.rc" ;
X        -d $rc || mkdir($rc,0777 & ~umask) || &Error("can't mkdir $rc ($!)") ;
X        $rc .= "/$prog" ;
X        -d $rc || mkdir($rc,0777 & ~umask) || &Error("can't mkdir $rc ($!)") ;
X        $rc .= "/$dump" ;
X      }
X    if ( -e $dump && ! -f $dump ) 
X      { &Warn("not a file: $rc") ;
X        $rc = '' ;
X      }
X    return $rc ;
X  }
X
Xsub exit
X  { if ( length($RC) )
X      { &dump_rc($RC) ; }
X    exit ;
X  }
X
Xsub put_stat
X  { local($stat,@stat) = @_ ;
X    @stat = split($;,$stat) ;
X    $stat[$MTIME] = sprintf("%+d", $stat[$MTIME]-$stat[$CTIME]) ;
X    $stat[$ATIME] = sprintf("%+d", $stat[$ATIME]-$stat[$CTIME]) ;
X    return join(' ',@stat) ;
X  }
X
Xsub get_stat
X  { local($stat,@stat) = @_ ;
X    @stat = split(' ',$stat) ;
X    $stat[$MTIME] += $stat[$CTIME] ;
X    $stat[$ATIME] += $stat[$CTIME] ;
X    return join($;,@stat) ;
X  }
X
Xsub dump_rc
X  { local($RC) = @_ ;
X    local($obj,$fil,@stat) ;
X    unless ( $modified )
X      { &Warn("no change, dumping anyway") ;
X      }
X    unless ( open(RC,">$RC") )
X      { &Warn("dump failed, can't write to $RC ($!)") ;
X	return 0 ;
X      }
X    else
X      { print RC "$MAGIC\n# Don't delete the first line\n" ;
X        for $obj ( @obj )
X          { printf RC "%s %s\n", $type{$obj}, $obj ;
X	    if ( $type{$obj} eq 'DIR' )
X	      { for $fil ( split($;,$stat{$obj}) )
X		  { printf RC "fil %s\n", $fil ;
X		    printf RC "stt %s\n", &put_stat($fil{$fil}) ;
X		  }
X	      }
X            elsif ( $type{$obj} eq 'FIL' )
X	      { printf RC "STT %s\n", &put_stat($stat{$obj}) ; }
X            elsif ( $type{$obj} ne 'UNK' )
X              { &Error("SYSERR: obj <$obj> has type <$type{$obj}>") ; }
X          }
X        close(RC) || &Warn("can't close $RC") ;
X        print "status dumped in $RC\n" ;
X	$modified = 0 ;
X	return 1 ;
X      }
X  }
X
Xsub read_rc
X  { local($RC) = @_ ;
X    local($what,$tail,@tail,$obj,$fil) ;
X    open(RC) || return ;
X    $what = <RC> ; chop $what ;
X    &Error("Error: first line in $RC should be:\n$MAGIC\nbut is\n$what") 
X      if $what ne $MAGIC ;
X    while ( <RC> )
X      { chop ;
X	next if /^#/ ;
X	$what = substr($_,0,3) ;
X	$tail = substr($_,4) ;
X	if ( $what eq 'DIR' )
X	  { $obj = $tail ;
X	    $type{$obj} = 'DIR' ;
X	  }
X	elsif ( $what eq 'fil' )
X	  { $stat{$obj} .= "$tail$;" ;
X	    $fil = $tail ;
X	  }
X	elsif ( $what eq 'stt' )
X	  { $fil{$fil} = &get_stat($tail) ; }
X	elsif ( $what eq 'FIL' )
X	  { $obj = $tail ;
X	    $type{$obj} = 'FIL' ;
X	  }
X	elsif ( $what eq 'STT' )
X	  { $stat{$obj} = &get_stat($tail) ; }
X	elsif ( $what eq 'UNK' )
X	  { $type{$tail} = 'UNK' ;
X	    $stat{$tail} = undef ;
X	  }
X	else
X	  { &Error("shouldn't occur in -f argument: $_" ) ; }
X      }
X    for ( keys %type ) { chop $stat{$_} if $type{$_} eq 'DIR' ; }
X    close(RC) || &Error("can't close $RC") ;
X  }
X
Xsub set_state
X  { %type = %stat = %fil = () ;
X    for ( @_ ) { &init_obj($_) ; }
X  }
SHAR_EOF
$TOUCH -am 0326164894 ptail &&
chmod 0750 ptail ||
echo "restore of ptail failed"
set `wc -c ptail`;Wc_c=$1
if test "$Wc_c" != "16275"; then
	echo original size 16275, current size $Wc_c
fi
# ============= ptail.1 ==============
echo "x - extracting ptail.1 (Text)"
sed 's/^X//' << 'SHAR_EOF' > ptail.1 &&
X.TH PTAIL 1L "RUU-CS"
X.SH NAME
Xptail \- watching the files grow
X.SH SYNOPSIS
X.B ptail
X[\fB-v\fP]
X[\fB-max\fP \fItime\fP]
X[\fB-log\fP \fIlog\fP]
X[\fB-f\fP \fIfile\fP]
X.I "[obj ...]"
X.br
X.B ptail
X\fB-once\fP
X\fB-f\fP \fIfile\fP
X.I "[obj ...]"
X.br
X.B ptail
X\fB-init\fP
X\fB-f\fP \fIfile\fP
X.I "[obj ...]"
X
X.SH DESCRIPTION
XProgram \fBptail\fP watches the filesystem objects specified in
Xthe argument list.
XAt any given moment, \fIobj\fP is either a plain file,
Xa directory (or globbing pattern if it contains any of '*?['),
Xor unknown.
X
XIf \fIobj\fP is a plain file, size changes are reported.
XIf the file is readable, data added to the file is shown.
XIf the file shrinks, the entire contents is shown.
X.br
XIf \fIobj\fP is a directory (or globbing pattern),
X\fIptail\fP watches all the plain files in the directory (or all the
Xplain files which are found when the globbing pattern is expanded).
X
XIf the type of an object changes, its old state is forgotten, and it
Xis treated as a new object of the new type.
XIf, for instance, an object changes from a plain file into a directory,
Xall the plain files in  the directory are shown
Xand watched for further changes.
X
XInitially, every object is inspected every second.
XBut if \fIptail\fP finds it hasn't changed,
X\fIptail\fP ignores the object for 2 seconds.
XIf the object hasn't changed after 2 seconds,
X\fIptail\fP ignores the object for 3 seconds, etc.
X.br
XIn general, \fIptail\fP keeps a timeout for every object.
XIf the timeout expires, \fIptail\fP inspects the object.
XIf the object has changed, \fIptail\fP reports
Xand resets the timeout to 1.
XIf the object hasn't changed,
X\fIptail\fP increments the timeout for the object
Xunless some upperbound (usually 60 seconds) is reached.
XSo every object is inspected at least every 60 seconds
X(use option \fB-max\fP to change this default 60).
X.SH OPTIONS
X.PP
XProgram \fBptail\fP recognizes the following options:
X.IP "\fB-v\fP"
XBe verbose, report stat info on every change.
X.IP "\fB-max\fP \fImax\fP"
XSet maximum time between inspections to \fImax\fP seconds.
X.IP "\fB-log\fP \fIlog\fP"
XLog all changes in file \fIlog\fP.
X.IP "\fB-f\fP \fIfile\fP"
XProgram \fBptail\fP keeps status info on all the objects it watches.
XIf the \fB-f\fP-option is specified, \fBptail\fP dumps this info
Xin file \fIfile\fP on normal exit.
XProgram \fBptail\fP can read such files on startup, report the changes
Xthat have occurred since \fIfile\fP was dumped,
Xand resume watching the objects.
X
XIf a \fIfile\fP starts with '=' (as is '=\fIname\fP') it is interpreted
Xas '\fIhomedir\fP/.rc/ptail/\fIname\fP', where \fIhomedir\fP is
Xthe value of environment variable HOME or, if HOME is not defined,
Xthe home-directory of the current real uid,
Xas reported by \fBgetpwuid\fP.
X.IP "\fB-once\fP"
XInspect and report only once.
XUseful only together with the \fB-f\fP-option.
X.IP "\fB-init\fP"
XIgnore previous state of objects found in state dumps.
XTake current state of objects as initial state.
XUseful only together with the \fB-f\fP-option.
X.PP
X.SH SIGNALS
XOn signal QUIT (usually the result of typing Control-\\), \fBptail\fP
Xexits without further ado.
X
XOn signal INTR (usually the result of typing Control-C), \fBptail\fP
Xsuspends its watching activities and enters command-mode.
XIn command mode, a menu is presented and the user may issue commands.
XOnly the first char of the command is significant.
X.IP "?"
XShow info on all commands and currents defaults.
X.IP continue
XLeave command mode and resume watching objects,
X.IP quit
XQuit \fBptail\fP, dump status if requested.
X.IP xit
XQuit \fBptail\fP immediately.
X.IP inspect
XInspect all objects, report changes if any.
X.IP report
XShow some status of all objects being watched.
X.IP Report
XShow some status of all objects being watched.
XAlso show status of files in directories and globs.
X.IP save
XSave current status. The user is prompted for for a filename,
Xcurrent default given.
XThe default changes to the filename given if the dump was succesful.
XIf a filename like '=\fIname\fP' is given it is interpreted
Xas '\fIhomedir\fP/.rc/ptail/\fIname\fP', where \fIhomedir\fP is
Xthe value of environment variable HOME or, if HOME is not defined,
Xthe home-directory of the current real uid,
Xas reported by \fBgetpwuid\fP.
X.IP background
XSuspend
X.BR ptail .
XUsualy the same as hitting Cntl-Z.
X.PP
X.SH "SEE ALSO"
Xperl(1), xtail(1), getpwuid(3C)
X.SH AUTHOR
XHenk P. Penning (Email: henkp@cs.ruu.nl)
X.br
XUtrecht University, Dept. Comp. Sci., the Netherlands.
X.SH ACKNOWLEDGEMENT
XChip Rosenthal provided us with \fBxtail\fP,
Xwhich was a great improvement over 'tail -f'.
XCompared to \fBxtail\fP,
X\fBptail\fP adds only a little functionality,
Xcould be considered more portable,
Xobserves a few more changes,
Xand is undoubtedly more expensive.
X.SH BUGS
XPlease report to the author.
X.SH FILES
X$HOME/.rc/ptail Default directory for storing stat-files.
X
SHAR_EOF
$TOUCH -am 0326164894 ptail.1 &&
chmod 0700 ptail.1 ||
echo "restore of ptail.1 failed"
set `wc -c ptail.1`;Wc_c=$1
if test "$Wc_c" != "4845"; then
	echo original size 4845, current size $Wc_c
fi
exit 0
--
Henk P. Penning, Dept of Computer Science, Utrecht University \__/  \__/  \
Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. \__/  \__/
Telephone: +31-30-534106, fax: 513791, NIC-handle: HPP1 \__/  \__/  \__/  \
e-mail : henkp@cs.ruu.nl (uucp to sun4nl!ruuinf!henkp) _/  \__/  \__/  \__/