#!/usr/bin/perl use Getopt::Long; # on xterm, Mac OS X Terminal, and probably others: # first digit: 3 = change foreground, 4 = change background # 9 = light foreground, 10 = light background # second digit: 0=black,1=red,2=green,3=yellow,4=blue,5=magenta,6=cyan,7=white # or a single digit: 0=plain, 1=bold, 4=underline, 7=reverse fg/bg my $MATCH_COLOR = "\e[31m"; my $FILENAME_COLOR = "\e[41m"; my $COLOR_END = "\e[0m"; sub HELP_MESSAGE { print STDERR <\$with_number, 'H|with-filename'=>\$opt_H, 'h|no-filename'=>\$opt_h, 'l|files-with-matches'=>\$files_only, 'L|files-without-match'=>\$files_without_only, 'binary-files=s'=>\$binary_files, 'r|R|recursive'=>\$should_recurse, 'c|color|colour'=>\$color, 'follow-links'=>\$follow_links, 'e=s' => \@patterns, 'i|ignore-case'=>\$opt_i, 'v|invert-match'=>\$opt_v, 'A|include-hidden'=>\$include_hidden, 'I|include=s'=>\@include, 'X|exclude=s'=>\@exclude, 'exclude-dir=s'=>\@exclude_dir, 'help'=>\$show_help, 'version'=>\$show_version); $SIG{INFO} = sub {$print_next_line = 1;}; if($show_help) { VERSION_MESSAGE(); HELP_MESSAGE(); exit 1; } if($show_version) { VERSION_MESSAGE(); exit 1; } warn "-h and -H are mutually exclusive\n" if $opt_H && $opt_h; warn "-l and -L are mutually exclusive\n" if $files_only && $files_without_only; warn "--color, -n, -h, and -H have no effect if lines aren't being displayed\n" if ($color || $with_number || $opt_h || $opt_H) && ($files_only || $files_without_only); if(@patterns == 0) { if(!@ARGV) { VERSION_MESSAGE(); HELP_MESSAGE(); exit 1; } @patterns = shift @ARGV; } @patterns = map $opt_i ? qr/$_/i : qr/$_/, @patterns; if(@exclude_dir != 0 || @exclude != 0 || @include != 0 || $include_hidden || $follow_links) { $should_recurse = 1; } if(!$include_hidden) { push @exclude, ".*"; push @exclude_dir, ".*"; } if(@include == 0) { @include = ("*"); } $with_filename = (@ARGV > 1) || $should_recurse; $with_filename = 1 if $opt_H; $with_filename = 0 if $opt_h; if(@ARGV == 0) { if($should_recurse) { @ARGV = ("."); } else { @ARGV = ("-"); } } sub globToRE { local $_; $_ = $_[0]; s/[^0-9a-zA-Z*\[\]\-?]/\\$&/g; s/\*/.*/g; s/\?/./g; s/\[\\\^/[^/g; # note: [?] and [*] probably won't work correctly return "^$_\$"; } sub matchesAGlob { local $_; $_ = shift; #s/.*\///g; for my $glob (@_) { my $re = globToRE($glob); return 1 if /$re/; } return 0; } my $bytenum; sub doMatch { for my $pattern (@patterns) { my $match = /$pattern/; return $opt_v unless $match; {use bytes; $bytenum = length $`;} } return !$opt_v; } sub doFile { local $_; my $name = $_[0]; if(-d $name) { if($print_next_line) { print STDERR "Current file: $name (directory)\n"; $print_next_line = 0; } if(!$should_recurse) { print STDERR "$name is a directory\n"; return; } my $dh; if(not opendir $dh, $name) { print STDERR "Cannot open directory $name\n"; return; } foreach my $fn (readdir($dh)) { next if !$follow_links && -l $fn; next if $fn =~ /^\.\.?$/; if(-d "$name/$fn") { next if matchesAGlob($fn, @exclude_dir); } else { next unless matchesAGlob($fn, @include); next if matchesAGlob($fn, @exclude); } doFile("$name/$fn"); } return; } #elsif(!-f $name && $name ne '-') { #print STDERR "$name is not a normal file\n"; #return; #} my $fh; if($name ne '-' && -B $name && ($binary_files eq "without-match" || $binary_files eq "ignore")) { return; } elsif($name ne '-' && -B $name && $binary_files eq "binary") { if($print_next_line) { print STDERR "Current file: $name (binary)\n"; $print_next_line = 0; } local $/; if(not open $fh, '<', $name) { print STDERR "Cannot open binary file $name\n"; return; } binmode $fh; $_ = <$fh>; my $match = doMatch(); if($files_only) { print "$name\n" if $match; } elsif($files_without_only) { print "$name\n" unless $match; } else { print "Binary file $name matches at byte $bytenum\n" if $match; } close $fh; return; } if($name eq '-') { open $fh, '-' or die "Can't open standard input\n"; } else { if(not open $fh, '<', $name) { print STDERR "Cannot open file $name\n"; return; } } my $matched_somewhere = 0; while(<$fh>) { if($print_next_line) { print STDERR "Current line: $name: $.: $_\n"; $print_next_line = 0; } my $match = doMatch(); next unless $match; if($files_only) { print "$name\n"; return; } elsif($files_without_only) { $matched_somewhere = 1; } else { if($color) { print "$FILENAME_COLOR"; for my $pattern (@patterns) { s/$pattern/$MATCH_COLOR$&$COLOR_END/; } } print "$name: " if $with_filename; print "$.: " if $with_number; print $COLOR_END if $color; print; } } if($files_without_only) { print "$name\n" unless $matched_somewhere; } close $fh; } for my $filename (@ARGV) { doFile($filename); }