1

I have a script that searches for a string and replaces it via the sed command. If that string contains special characters, the script will escape them (except for the slash because it's my current delimiter for sed and the column because it marks the string on the bash line).

Here it goes:

raw_searchstring='SearchForThis';
raw_replacementstring='ReplaceWithThis';

#Escape special characters:
quoted_searchstring=$(printf %s "$raw_searchstring" | sed 's/[][()\.^$?*+]/\\&/g');
quoted_replacementstring=$(printf %s "$raw_replacementstring" | sed 's/[][()\.^$?*+]/\\&/g');

find ./ -type f -exec sed -i -r "s/$quoted_searchstring/$quoted_replacementstring/" {} \;

I tested this on Ubuntu and it worked just fine.

However, I need to run the script on a AIX system. Since it does not support in-line editing with sed -i I tried the following, as suggested on a similar question here (AIX's sed - in place editing):

find ./ -type f -exec sed -r 's/$quoted_searchstring/$quoted_replacementstring/' infile > tmp.$$ && mv tmp.$$ infile {} \;

This is where I get the error

find: missing argument to `-exec'

so I tried to pass more than one -exec statement to find with this line:

find /home/tobias/Desktop -type f -exec sed -r 's/$quoted_searchstring/$quoted_replacementstring/' infile > tmp.$$ {} \; -exec mv tmp.$$ infile {} \;

that doesn't work either:

sed: can't read infile: No such file or directory

I am not sure what I did wrong. Can you help me fix this line of code or point me in the right direction?

1 Answers1

2

Your attempts aren't working because you're trying to use shell operators such as && and > in the command executed by find, but you're typing those operators directly in the command, so they're executed by the shell that's calling find. Your commands are parsed as

find … > tmp.$$ && mv …

e.g. the first find invocation is

find ./ -type f -exec sed 's/$quoted_searchstring/$quoted_replacementstring/' infile

with output redirected to tmp.$$. There are other problems with the command: infile should be {} (it's the file that find found), and the single quotes around the sed expression should be double quotes since you're using shell variables in there.

Since you need to use shell constructs in the command that's executed by find, tell find to execute a shell.

find … -exec sh -c '…' {} \;

To avoid quoting difficulties, pass things that need quoting, such as the sed expression, as arguments to sh.

find ./ -type f -exec sh -c '
    sed "$0" "$1" >"$1.new" && mv "$1.new" "$1"
  ' "s/$quoted_searchstring/$quoted_replacementstring/" {} \;

For a slight performance gain at a slight legibility loss, you can use the -exec … {} + form and a shell loop.

find ./ -type f -exec sh -c '
    for x; do
      sed "$0" "$x" >"$x.new" && mv "$x.new" "$x";
    done
  ' "s/$quoted_searchstring/$quoted_replacementstring/" {} +

Alternatively, if the version of ksh93 on your AIX isn't too ancient, you can use its recursive globbing feature (introduced in ksh93p).

set -G
for x in **; do
  [[ -f $x ]] || continue
  sed "s/$quoted_searchstring/$quoted_replacementstring/" "$x" >"$x.new" && mv "$x.new" "$x";
done

In any case, note that you'll also need to tweak your string-to-regexp conversion to produce a basic regular expression since the -r flag to use ERE is a GNU extension. Also your existing code has a few bugs: you forgot to quote slashes, and you aren't quoting the right characters in the replacement text.

quoted_searchstring=$(printf %s "$raw_searchstring" | sed 's![[\/.^$*]!\\&!g');
quoted_replacementstring=$(printf %s "$raw_replacementstring" | sed -e 's![][\/&]!\\&!g' -e '!$ s!$!\\!');