#!/usr/bin/perl -w
#
# sub - search strings using Perl regular expressions and substitute replacement strings.
#	Only files matching the search regex will be modified.
#	String matches may be done over end-of-lines (extremely useful!).
#
# ARGUMENTS:
#	Takes two regular expressions plus optional filenames as arguments.
#	If no filenames are specified, input is on STDIN and output is on STDOUT.
#	If STDIN is the input, there will ALWAYS be output on STDOUT, even if the regex
#	did not match.  This allows sub to be used for piping.
# OUTPUT:
#	All status output is directed to STDERR.
#	The number of search regex matches for each file name is reported.
# ENVIRONMENT:
#	Set the environmental variable $DEBUG if you want to see what is going
#	on without changing any files.
#	Set $PRESERVE_TIME if you want to change files without changing their
#	modification times.
#	Set $SIMULATE if you just want a count of changes to be made without changing
#	file contents.  This breaks pipe treatment currently.
# LICENCE:
# 	This software is in the PUBLIC DOMAIN.
# HISTORY:
# 	First version written by Erik Rossen <rossen@rossen.ch>, November 17, 2001.
#	2002-02-14 added PRESERVE_TIME option
#	2005-07-13 added SIMULATE option

use File::stat qw(:FIELDS);

$debug=$ENV{'DEBUG'};
$defdebug=defined $debug;

$ptime=$ENV{'PRESERVE_TIME'};
$defptime=defined $ptime;

$simulate=$ENV{'SIMULATE'};
$defsimulate=defined $simulate;

if (@ARGV < 2) { die "Usage: sub 'search_Perl_regex' 'replace_Perl_regex' [file] ...\n" };
$search=shift;
$replace=shift;
# Tip: the file named "-" is STDIN when opened by Perl's open() function
if (@ARGV == 0) { push @ARGV, '-' ; }

printd("$0\n=== replace:\n\"", $search, "\"\n=== with:\n\"", $replace, 
	"\"\n===in files:\n", join("\n",@ARGV), "\n");

# Tip: undefining the "input-end-of-record" variable makes Perl suck in files with one <>
undef $/;

while ($fn=shift) {
	printd("\n==> $fn <==\n");
	if (open (F, "< $fn")) {
		$content=<F>;
		$length1=length($content);
		stat($fn) if ($fn ne '-' && $defptime);  # st_atime and st_mtime should now be filled...
		close F;
	} else {
		$error = $!;
		printe("$0: $fn: Unable to open for read: $error\n");
		next;
	}
	printd("=== before:\n", $content);

# *** THE FOLLOWING LINE IS THE HEART OF THIS SCRIPT ***
	$n = ( eval "\$content=~s/$search/$replace/gs" || 0 );

	$length2=length($content);
	$length_change=$length2-$length1;

	if ($defsimulate) {
		printe($n, "\t", $fn, "\tSIMULATED length_change=$length_change\n"); 
		next; # skip actual writing to the file.  This breaks pipe treatment.  FIX!
	} else {
		printe($n, "\t", $fn, "\n");
	}

	printd("=== after:\n", $content);
	if ( $n > 0 || $fn eq '-') {
		if ( (!$defdebug || $fn eq '-') && open(F, "> $fn") ) { 
			print F $content;
			close F;
			utime $st_atime, $st_mtime, $fn if ($fn ne '-' && $defptime);
		} else {
			$error = $! || "In DEBUG mode";
			printe("$0: $fn: Unable to open for write: $error\n");
		}
	}
}

# miscellaneous functions that I like to factor out
sub printe { print STDERR @_; }
sub printd { print STDERR @_ if $defdebug; }

