#!/usr/bin/perl ############################################################################# # # Program: # ftrunc # Author: # Christian J. Robinson # Copyright: # July 06, 2007 -- GPLv2 or later. # Purpose: # Truncate a file or files at the first/Nth occurrence of a string. # Assumptions: # None # Exit values: # 0 for normal execution # 1 for usage error # Known Limitations: # - The count can't be something like "last / -1 / -N". # # TODO : # - Allow the count to be "last"? # - Full blown man page, ala "notify"? # # RCS: # # $Id: ftrunc,v 1.4 2007/07/07 05:35:01 infynity Exp $ # ############################################################################# use 5.008; use strict; use Getopt::Long; use File::Basename; use vars qw($BASENAME %args $line $string $printing $count @matches $nmatches); sub usage($); $BASENAME = basename($0); $args{'count'} = 1; Getopt::Long::config qw/bundling/; GetOptions(\%args, 'count|n=i', 'ignorecase|i' => sub{$args{'ignorecase'} = 'i';}, 'matchcase|m' => sub{delete($args{'ignorecase'});}, 'beginning|head|b', 'end|tail|t' => sub{delete($args{'beginning'});}, 'exclusive|excludestring|e', 'inclusive|includestring|s' => sub{delete($args{'exclusive'});}, 'linewise|l', 'characterwise|c' => sub{delete($args{'linewise'});}, 'global|g', 'eachfile|f' => sub{delete($args{'global'});}, 'quiet|silent|q', 'verbose|v', => sub{delete($args{'quiet'});}, 'help|h' => sub{usage(0);}, ) or usage(1); $string = shift or usage(1); $string =~ s/\\E/\\\\E/g; $count = 0; $printing = !$args{'beginning'}; if (scalar(@ARGV) > 1 && !$args{'quiet'}) { print "==> $ARGV[0] <==\n"; } while ($line = <>) { if ($count != -1) { eval '@matches = $line =~ m/\Q$string\E/g'.$args{'ignorecase'}; $nmatches = scalar(@matches); $count += $nmatches; } if ($count >= $args{'count'}) { my $i = $args{'count'} - $count + $nmatches; unless ($args{'linewise'}) { if ($args{'beginning'} && $args{'exclusive'}) { eval '$line =~ s/.*?(\Q$string\E(.*)){' . $i . '}/$2/'.$args{'ignorecase'}; } elsif ($args{'beginning'} && !$args{'exclusive'}) { eval '$line =~ s/.*?(\Q$string\E.*){' . $i . '}/$1/'.$args{'ignorecase'}; } elsif (!$args{'beginning'}) { eval '$line =~ s/((?:.*?\Q$string\E){' . $i . '}).*/$1/'.$args{'ignorecase'}; eval '$line =~ s/\Q$string\E$//'.$args{'ignorecase'} if $args{'exclusive'}; } else { die "Never should have gotten here!"; } } $printing = !$printing; $count = -1; print $line unless $args{'exclusive'} && $args{'linewise'}; exit if $args{'global'}; next if $args{'beginning'}; } print $line if $printing; } continue { if (eof) { print "\n==> $ARGV[0] <==\n" if $ARGV[0] && !$args{'quiet'}; unless ($args{'global'}) { $printing = !$printing if $count == -1; $count = 0; } } } sub usage($) { my $rval = shift; my ($where, $pager, $out, $ttylines, $i); $pager = ($ENV{'PAGER'} ? $ENV{'PAGER'} : 'less'); $ttylines = (split(' ', `stty size`))[0]; my @usage = ( '--count, -n N' => ' Truncate at "count"th occurrence of "string". (default 1)', '--ignorecase, -i' => ' Ignore case in "string".', '--matchcase, -m' => ' Match case in "string". (default)', '--beginning, --head, -b' => ' Truncate from the beginning of the file up to "string".', '--end, --tail, -t' => ' Truncate from "string" to the end of the file. (default)', '--excludestring, --exclusive, -e' => ' Exclude "string" from the result.', '--includestring, --inclusive, -s' => ' Include the "string" from the result. (default)', '--linewise, -l' => ' Operaite line-wise; truncation will happen on the line that contains "string".', '--characterwise, -c' => ' Operate character-wise; truncation will happen at the location of "string". (default)', '--global, -g' => ' Stop processing the moment "string" is encountered, even if there are more files which have not been examined, and the count is cumulative between files.', '--eachfile, -f' => ' Process each file separately. Each file will be handled individually and truncated at "string". (default)', '--quiet, --silent, -q' => " Don't print headers of filenames if more than one file is being processed.", '--verbose, -v' => ' Print headers of filenames if more than one file is being processed.', ); $out = "Usage: $BASENAME [options] string [files]\n\nOptions:\n"; if ($rval) { for ($i = 0; $i <= $#usage; $i+=2) { $out .= " $usage[$i]\n"; } $out .= " --help, -h\n"; } else { for ($i = 0; $i <= $#usage; $i+=2) { $out .= " $usage[$i]" . $usage[$i+1] . "\n\n"; } $out .= " --help, -h\n Print this usage statement and exit.\n"; } my $lines = ($out =~ tr/\n//); if ($rval) { $where = *STDERR; } elsif ($lines >= $ttylines) { require IO::File; $where = new IO::File "| $pager"; } else { $where = *STDOUT; } print $where $out; exit $rval; } __END__ #!/bin/sh if [ "$#" -lt "1" ] then echo "Usage: ${0##*/} string [file]" >&2 exit fi string="$1" shift sed -e " s/\\(.*$string\\).*/\\1/ /$string/ q " $*