Conditional Recursive Search & Replace?

I'm using cygwin bash on windows 10.

Does anyone have a script to recursively traverse a directory looking for files that contain a specified search pattern and perform a single search and replace on the file
(1) only if the file contains one or more occurrences of a specified pattern
(2) only reads and writes a given file once even when that file contains multiple occurrences of the specified search pattern
(3) replaces all the occurrence's of the search pattern with the replacement text.

Google searching revealed lots of scripts to do this but they blindly read and wrote every file at least once and more times for every occurrence of the search pattern.

I would like a conditional search and replace. I'm sure this script has been written many times already.
Perl works, bash works, sed works...

Thank you,

Siegfried

Can the specified pattern be anywhere in the file ?

Lets say you have a file with jack and jackhammer.
And you wish to replace a string jack with john, should the result be john and johnhammer or john and jackhammer ?

But more information is required as your request can be very string specific so to say.

Operation should be doable with find with -exec or xargs and sed.
Any attemps from your side ?

Regards
Peasant.

1 Like

Peasant,
(1) the result should be johnhammer. I should be able to use regular expressions.

Operation should be doable with find with -exec or xargs and sed .

Yeah -- I've been using find/xargs/sed for years. The problem is how to avoid making superfluous copies of files that don't contain "jack". Making superfluous copies is slow.

This solution has the same problem:

 find . -t file -exec perl -pi.bak -e 's/blue/red/g' {} \;

That search and replace syntax that is implemented by perl and sed is nice, however, it does not give you a way to detect if it was successful.

This is working

find . -type f  -not -path './.git'  -exec perl -e '$filename=shift @ARGV; $pattern=shift @ARGV; $replace= shift @ARGV; $_ = do{local(@ARGV,$/)=$filename;<>}; if ( /$pattern/ ) { s/$pattern/$replace/g ; open(FH, ">", $filename) or die $!; print FH $_; close(FH); print "write $filename\n"; } else { print "pattern not found in $filename\n" }' {} red blue \;

(2) Why does this ignore my "-not -path" and traverse my ".git" directory?

(3) Is there a simpler way to do this with find | grep | uniq | sed?

If grep finds a file with multiple occurrences of jack we only want to run sed once on that file. How can we make grep stop when it finds the first occurrence of jack?

Thanks

Siegfried

There's a long running discussion of how to detect sed's success/failure with multiple options and/or workaround and some deferenced links here as well.
A bit hairy I'd say, but something to enlighten oneself with.

Because -path is a filter, not an action.
Replace

find . -type f  -not -path './.git' -print

with

find . -path './.git' -prune -o -type f -print

The -prune is the prune=skip action. Because it is true when pruned one must continue with -o or -or (or, otherwise).

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.