Many Questions
Many Questions

Reputation: 125

Bash - sed syntax with variables

I've got two variables VAR1 and VAR2 that contain strings. What I want to do is go through a list of files that have a .txt extension and change all occurences of VAR1 to VAR2. So far, it looks like this:

for i in `find . -name "*.txt"`
do
 echo $i
 sed -i -E "s|\$VAR1|\$VAR2|g" $i
done

I think everything except the sed line is working well. I think it's a syntax issue, but I haven't been able to figure out what it is. Any help would be appreciated Thanks

Upvotes: 1

Views: 464

Answers (4)

tripleee
tripleee

Reputation: 189327

You managed to quote the dollar sign from the shell (which would not have been necessary if you had used single quotes instead of double) but this does not change the fact that dollar signs also have a meaning in regular expressions. Double the backslashes to escape from both the shell and sed, or use single quotes so the backslashes get through to sed. Alternatively, use a notation which does not require backslashes.

sed -i -E 's|[$]VAR1|$VAR2|g' "$i"

Incidentally, your loop has a number of problems. Your for loop will not work correctly if there are file names with whitespace in them, and you need to quote the arguments inside the loop. To completely cope with file names with special characters in them, you want to use find -exec instead.

find . -name "*.txt" -exec sed -i -E 's|[$]VAR1|$VAR2|g' {} \;

If your find supports \+ instead of \;, by all means use that.

Upvotes: 1

peak
peak

Reputation: 116690

Since sed's "find-and-replace" functionality is oriented to regular expressions rather than literal strings, you might wish to consider an alternative to sed, e.g. using awk as follows:

 awk -v from="$VAR1" -v to="$VAR2" '
   function replace(a,b,s,  n) {
     n=index(s,a);
     if (n==0) {return s}
     return substr(s,1,n-1) b replace(a,b, substr(s,n+length(a)));
   }
   {print replace(from, to, $0)} '

The above can easily be combined with the find ... | while read f ; do .... done pattern mentioned elsewhere on this page.

GNU awk supports the equivalent of sed's '-i' option, but it's probably better simply to direct the output of awk to a temporary file, and then mv it into place.

Upvotes: 1

peak
peak

Reputation: 116690

(1) Using the idiom for i in $(find ....) ; do ...; done will often work as intended, but it is not robust. Significantly better is the pattern:

find ... | while read i ; do ... ; done

(2) If $VAR1 and/or $VAR2 contain characters that have special significance in regular expressions, then some care will be required. For example, parentheses ("(" and ")") have special significance, and so if VAR1 contains these, using the -r option (or on a Mac, the -E option) is probably asking for trouble.

(3) Chances are that sed -i -e "s|$VAR1|$VAR2|g" will do the trick if VAR1 does not contain any of the eight characters: ^$*[]\|. and if VAR2 does not contain "|", "\" or "&".

(4) If you want to prepare your strings ($VAR1 and $VAR2) programatically for use with sed, then see this SO page; it shows how to munge the strings -- using sed of course!

Upvotes: 0

Martin Konecny
Martin Konecny

Reputation: 59591

You shouldn't need to escape your $ variable. Also make sure to use the lower case -e and quote the filename in case it has spaces:

sed -ri -e "s|$VAR1|$VAR2|g" "$i"

Upvotes: 2

Related Questions