4

I want to replace a string found in a file with another string, but both have a special character (in this example it is a . char) e.g 1.0 and 2.0.

So this is the command currently used:

sed -i 's/1\.0/2\.0/g' /home/user1/file1.txt

What if 1.0 and 2.0 were saved in variables $i and $j? where i has the value 1.0 and j has the value 2.0, how can I still replace i with j?

  • 1
    No need to escape the dot in the RHS (see also What characters need escaping when using sed). In this particular case (only the dot needs escaping), you could run: sed "s/${i//./\\.}/$j/g" infile. – don_crissti Mar 12 '15 at 4:01
  • @don_crissti - this is true, but I usually find it easier to apply escapes indiscriminately when I do at all. It can be pretty tedious, to, for example, handle & or . differently depending on context. I think that maybe I am not alone in this regard - and this is why RHS, [aic] all require two backslashes to represent one literally even when they can serve no other purpose in context. At least, that's my assumption. I can't figure on another reason for it. – mikeserv Mar 12 '15 at 4:29
  • 1
    duplicate of askubuntu.com/q/595780/10127 – glenn jackman Mar 12 '15 at 10:59
  • Ugh, sorry about the edit/rollback, I hadn't realized the script you were showing already used mike's approach. – terdon Mar 12 '15 at 11:43
1

You can just use sed to do the fixup for you:

printf '%s\n' "$i" "$j" |
sed 's/[]\$*&/.^[]/\\&/g;H;$!d
     x;y|\n|/|;s|.*|s&/g|' |
sed -f - /path/to/infile

So this s///ubstitution will escape any/all BRE metacharacters in input:

s/[]\$*&/.^[]/\\&/g

...by prefixing each with a backslash. The first sed then saves a copy of the first line - $i - in Hold space by prepending a \newline character to it. The $i line is then deleted because it is !not the $last. The next line - the $j line - is also the last, and after it gets the same treatment as the first, it is not deleted. Instead, it exchanges the hold and pattern buffers and operates on the concatenated results. At this point pattern space looks like:

\n1\.0\n2\.0

...so we y/// translate all \newlines for / slashes, s///ubstitute .*all of pattern-space for &itself plus a prepended s and an appended /g which gets us:

s/1\.0/2\.0/g

This is then autoprinted to the second sed which is reading stdin - or -f - - as its script. When the first sed is finished and closes the pipe between them, the second sed starts applying...

s/1\.0/2\.0/g

...to every line in its named input file - which is here /path/to/infile.

I wrote your file like this:

printf '%04s%04s%04s%04s\n' \
        0 0 -1 0 1 0 0 0 0 -1\
        0 0 1.5 2.0 1.0 0 >/tmp/temp

Which got me a file like...

   0   0  -1   0
   1   0   0   0
   0  -1   0   0
 1.5 2.0 1.0   0

I then wrote a different version of your script like:

ii=0.0
for i in        1.0 2.0 3.0 4.0
do      str2=$i
        printf '\033[41m## %s \033[0m\n' \
                "str2 = $str2" "$ii $str2"
        printf %s\\n "$ii" "$str2"
        ii=$str2
done | 
sed '   s/[]\$^&*./[]/\\&/g;H;x
        s|^\(\n\)\(.*\)\n\(.*\)\n\(.*\)\n\(.*\)|\
        bs\5\1:i\5\1i\\\1\2\\\1\3\1:s\5\1s/\4/\5/gp;ti\5|p
        s|||;h;d' |
sed -f - /tmp/temp

Which uses the shell only to generate the strings, but allows sed to do all of the data processing. Notice that though two seds are called each is only ever called once.

When I run it the results are:

   0   0  -1   0
   1   0   0   0
   0  -1   0   0
 1.5 2.0 2.0   0
## str2 = 2.0 
## 1.0 2.0 
 1.5 3.0 3.0   0
## str2 = 3.0 
## 2.0 3.0 
 1.5 4.0 4.0   0
## str2 = 4.0 
## 3.0 4.0 
 1.5 4.0 4.0   0

The lines beginning with # are colored red as I expect you mean them to be. sed only writes them when a s///ubstitution is successful. The script that the first sed writes for the second looks like:

                bs1\.0
:i1\.0
i\
[41m## str2 = 1\.0 [0m\
[41m## 0\.0 1\.0 [0m
:s1\.0
s/0\.0/1\.0/gp;ti1\.0

                bs2\.0
:i2\.0
i\
[41m## str2 = 2\.0 [0m\
[41m## 1\.0 2\.0 [0m
:s2\.0
s/1\.0/2\.0/gp;ti2\.0

                bs3\.0
:i3\.0
i\
[41m## str2 = 3\.0 [0m\
[41m## 2\.0 3\.0 [0m
:s3\.0
s/2\.0/3\.0/gp;ti3\.0

                bs4\.0
:i4\.0
i\
[41m## str2 = 4\.0 [0m\
[41m## 3\.0 4\.0 [0m
:s4\.0
s/3\.0/4\.0/gp;ti4\.0

Note though that that though it appears the [ strings are not escaped this is just the effect of my terminal on the output - which winds up eating the char immediately following \033. When the second sed receives the script the input is like \033\[... but the output it inserts to stdout is \033[...

  • could you please comment or explain your answer as I'm a bit lost? :) here is my scenario again: I have two variables $i and $j that have the values 1.0 and 2.0, and I want to replace every occurence of $i (which is 1.0) in file text1.txt with the value of $j (which is 2.0) – Tak Mar 12 '15 at 3:57
  • @shepherd - I hope the edit helps? – mikeserv Mar 12 '15 at 4:21
  • I've updated my question to include the whole shell file and everything. I'd really appreciate it if you could have a look and advise me please. Thanks in advance – Tak Mar 12 '15 at 4:39
  • @shepherd is there any reason why the ' hard-quotes would be misinterpreted? What happens if you double up the backslashes in the first s///? Is this being evaled in anyway? A prompt maybe? Or an alias? I created your example like printf %04s%04s%04s%04s\\n 0 0 -1 0 1 0 0 0 0 -1 0 0 1.5 0.0 1.0 1 >test then ran the above verbatim and the only changed value was the second to last. – mikeserv Mar 12 '15 at 5:01
  • Can you please try using the same file I posted in my question and the shell script I posted (based on your answer) as when I try it the file is not changed? – Tak Mar 12 '15 at 5:42
1

try this: Escape all . by replacIng all . to \.

str1="$( echo -n $i | sed 's/\./\\\./g' )"  

repeat same for str2 using $j . then replace as follows

sed "s/$str1/$str2/g"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.