#!/usr/bin/perl # # retail - regular expression capable tail # # This script emulates tail (include -f option), allowing monitoring # of multiple files at once. It also allows for an array of regex # tests to be defined. Each of these regex tests would have a subroutine # associated with it. Should the regex match a line of output, the code # would be executed with the line as an argument. # # The regex array should be structured as such: # # @REGEX = [ # [ qr/regularexpression/opts, sub { do something; print @_; } ], # [ qr/regularexpression/opts, sub { do somethingelse; print @_; } ], # [ qr/regularexpression/opts, sub { do something; print BOLD GREEN @_; } ], # ]; # # This and other hacks can be found at: http://oddgeek.info/ # # Copyright (c) 2005 Jason A. Dour # # This software is provided 'as-is', without any express or implied warranty. # In no event will the authors be held liable for any damages arising from the # use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software in a # product, an acknowledgment in the product documentation would be # appreciated but is not required. # # 2. Altered source versions must be plainly marked as such, and must not # be misrepresented as being the original software. # # 3. This notice may not be removed or altered from any source # distribution. # # # Version Information # # 1.0 2005.05.31 # # Put in a few additional comments. Cleaned up formatting. There seem # to be a LOT of similar scripts out there now. Wow. # # primordial ooze # # Used off and on over the years to watch logfiles for important messages # or when debugging something. When used with Term::ANSIColor and GNU # Screen, it works as a great log-watcher window. When I first wrote # this, I couldn't find other scripts on the net. # # # Necessary modules. use Getopt::Regex qw(GetOptions); use Term::ANSIColor qw(:constants); use File::Tail; # # Global Environment or Configuration # # Ensure ANSI colors reset after each use. $Term::ANSIColor::AUTORESET = 1;; # # $progname # The name of the script. $progname = $0; $progname =~ s/.*\///; # # usage() # Pretty-print the usage for the user. sub usage { print STDERR < -1 ); # # Check to see if there were multiple files requested. if ( $#ARGV > 0 ) { $Options{'MultiFile'} = 1; } else { $Options{'MultiFile'} = 0; } # # Number of lines? Default is 10. $Options{'NumLines'} = 10 unless ( length($Options{'NumLines'}) ); # # Number of seconds to sleep between EOF checks. $Options{'Sleep'} = 1 unless ( length($Options{'Sleep'}) ); # # Check for insanity in Sleep option. if ( $Options{'Sleep'} == 0 ) { error(99, "Insane sleep value of 0 - set greater than zero."); } # # Check for alternate config file. if ( length($Options{'ConfigFile'}) ) { error(99,"Do not have access to config file ($Options{'ConfigFile'}).") unless ( -f $Options{'ConfigFile'} && -r $Options{'ConfigFile'} ); do $Options{'ConfigFile'} || error(99,"Error parsing config file ($Options{'ConfigFile'})."); } elsif ( ( -f "$ENV{'HOME'}/.retailrc" && -r "$ENV{'HOME'}/.retailrc" ) ) { do "$ENV{'HOME'}/.retailrc" || error(99,"Error parsing config file (~/.retailrc)."); } return(1); } # # MAIN # # Get the command-line options. GetOptions( \@ARGV, ['^-([0-9]+)$', sub { $Options{'NumLines'} = $1; }, 0], ['^-c$', sub { if ( length($_[0]) ) { $Options{'ConfigFile'} = $_[0]; } else { usage("No config file specified."); } }, 1], ['^-f$', sub { $Options{'Follow'} = 1; }, 0], ['^-s$', sub { if ( length($_[0]) ) { $Options{'Sleep'} = $_[0]; } else { usage("No sleep interval specified."); } }, 1], ['^-s([0-9]+)$', sub { $Options{'Sleep'} = $1; }, 0], ['^(-.*)$', sub { usage("Invalid option ($1)."); }, 0], ); usage("Invalid syntax.") unless optok(); # # Don't buffer STDOUT $| = 1; # # For each file specified on the command line, loop once, # creating File::Tail instances for each. for ( $loop = 0; $loop <= $#ARGV; $loop++ ) { # # Open the file for reading. $FILES[$loop] = File::Tail->new( name => "$ARGV[$loop]", maxinterval => $Options{'Sleep'}, interval => $Options{'Sleep'}, adjustafter => 1, tail => $Options{'NumLines'}, reset_tail => -1, ); # # Assign name to array indexed on filehandles. $NAMES{$FILES[$loop]} = "$ARGV[$loop]"; } # # Set flag to prevent needless headers. $lastprt = $FILES[$#ARGV]; # # Loop once or forever, depending on Follow being specified. do { # # Select those files that have data waiting to be read. ($dsr, $remtime, @DSRFILES) = File::Tail::select(undef, undef, undef, 60, @FILES); # # For each of the files from above, loop once to read new data. foreach $file ( @DSRFILES ) { # # Header if necessary. if ( $Options{'MultiFile'} && ($lastprt != $file) ) { print BOLD YELLOW "***** $NAMES{$file} *****\n"; } # # Loop through each line of input from file. line: foreach $line ( $file->read ) { # # Check for regular expression array to be i # used for operating on lines. if ( defined(@REGEX) ) { # # Apply every regex couplet. foreach $regex (@REGEX) { # # If the regex hits, pass $line to the code... if ( $line =~ /(?-xism:$regex->[0])/ ) { $code = $regex->[1]; &$code($line); undef($code); next line; } } } # # If no array or match, print as normal. print $line; } # # Set flag for last file that was printed. $lastprt = $file; } # # Destroy per-loop variables. undef($dsr); undef($remtime); undef(@DSRFILES); } while ( $Options{'Follow'} ); # # We're done... exit(0);