Reputation: 61
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
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.
sed
substitutions so we can unambiguously nest them.
%
or /
could occur in the patterns, obviously choose yet another separator.sed
script. This means all the backslashes have to be doubled to prevent them from being "eaten" by the double quotes.find
results to a simple sh -c
script.
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.
/dev/stdin
instead of -
maybe.sed
simply refuses to read a script from standard input. Use a temporary file or some other workaround.Upvotes: 2
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