avish
avish

Reputation: 61

How can execute sed on file by file basis using result of find?

I need to perform a replace on a file by searching for a pattern and the replace is obtained by executing another command that provides the string to use instead as a replacement. However this also needs to be done on each file in the directory. The following syntax does not work as sed has no input file.

find . type f -exec sed -i "s/<pattern1>/$(sed -n 's/abcd\(pattern2\)efgh/\1/p')/g" {} \;

The idea is the internal sed returns pattern2 for the external sed to use as a replacement for pattern1. However I need to do this on each file in the directory. I am unable to figure out the syntax.

If I use find for the inside sed,

find . type f -exec sed -i "s/<pattern1>/$(find . -type f -exec sed -n 's/abcd\(pattern2\)efgh/\1/p' {} \;)/g" {} \;

I end with an error as sed: -e expression #1, char 53: unterminated 's' command.

Could I get some help with this?

Upvotes: 1

Views: 1496

Answers (2)

tripleee
tripleee

Reputation: 189487

If you are assuming that each found file will contain a single match for abcd\(pattern2\)efgh and you would like to pull out pattern2 from there and use it as the thing to replace pattern1 with in the same file, that would be something like what user unknown's original answer looked like. Here is an attempt at repainting that particular bike shed.

find . -type f -exec sh -c 'for f; do
    sed -n "s%abcd\\(pattern2\\)efgh%s/<pattern1>/\\1/g%p" "$f" |
        sed -i -f - "$f"; done' _ {} +

A few things can be highlighted here.

  • We use different separators in the sed substitutions so we can unambiguously nest them.
    • If % or / could occur in the patterns, obviously choose yet another separator.
  • Because we cannot nest single quotes, I use doube quotes around the sed script. This means all the backslashes have to be doubled to prevent them from being "eaten" by the double quotes.
  • We pass find results to a simple sh -c script.
    • For efficiency, I refactored it to loop over found files so you can avoid spawning a new shell process for each found file. In simpler terms, without the loop, this would be find ... -exec sh -c 'sed -n "s%foo%s/moo/bar/%p" "$1" | sed -i -f - "$1"' _ {} \;
  • sed -f - says to read the script from standard input. This is not portable.
    • On platforms where this doesn't work, try /dev/stdin instead of - maybe.
    • However, e.g. on MacOS, the default sed simply refuses to read a script from standard input. Use a temporary file or some other workaround.

Upvotes: 2

user unknown
user unknown

Reputation: 36229

If in doubt, sacrifice briefness for readability. Here is an ad-hoc script, ad-hoc.sh (after discussion in comments and chat):

#!/bin/bash
file="$1"
patternInner=$(sed -n -r "s/.*class\s*(.*)\s*:\s*public.*/\1/p" "$file")
if [[  -n "$patternInner" ]]
then
    echo -e "$patternInner matching in $file" >> findsed.log 
    # sed -i "s/void\s*\(test.*\)\s*()/MWTEST($patternInner, \1)/" "$file"
    sed "s/void\s*\(test.*\)\s*()/MWTEST($patternInner, \1)/" "$file"
else
    echo patternInner empty for file $file >&2
fi

Modifications: sed-expression from real life. Test for non-empty patternInner.

Some kind of logging is done, if a pattern is found, name of file, pattern which matched.

Don't forget to chmod a+x ad-hoc.sh. If the tests look valid, you have to activate the line with sed -i instead of the one without -i, to make the changes not just happen on the screen, but in the file.

Usage:

for f in *.?pp # would work for a flat directory as well as find.
do
    ./ad-hoc.sh "$f"
done 2> forsed.err

or with find, if you insist:

find . -type f -name "*.?pp" -exec ./ad-hoc.sh {} ";" 2> findsed.err

Of course it could be put into single quotes and executed with

... -exec bash -c '....(ad-hoc.sh 
     code 
     here)' {} ";" 

but I prefer ad-hoc-scripts in such cases. Less masking trouble. Better editable, better testable.

Depending on the variability of $patternInner, masking has to be considered, but maybe it's just characters and numbers, no dollars, asterisk and backslashes, I hope.

Upvotes: 2

Related Questions