SED replace across multiple lines

Solution 1:

Sed works on a line-by-line basis. It can be made to work on multiple lines, but it wasn't designed that way - and in my opinion it definitely shows when you attempt to use like that. But if you decide to go that way you will probably have to use registers. Check some of the solutions to https://stackoverflow.com/questions/1251999/sed-how-can-i-replace-a-newline-n to see how it can be done.

I prefer to use perl instead of sed for this kind of task (multi-line-oriented, I mean). The boilerplate you have to add before the search-and-replace (BEGIN...) is not obvious, but the regex seems cleaner to me:

perl -i.bak -pe 'BEGIN{undef $/;} s/<!--string-->/string/smg' file.xml

Or, using grouping to shorten the expression and to allow you to use a regex there:

perl -i.bak -pe 'BEGIN{undef $/;} s/<!--(string_or_regex)-->/\1/smg' file.xml

It should work both on occurrences with and without newlines between the comment markers and the code to be uncommented.

Adapted from:

https://stackoverflow.com/questions/1030787/multiline-search-replace-with-perl

Solution 2:

sed reads each line in turn, so it'll never match a multiline pattern unless you nudge it in the right direction. The N command reads one line from the input and appends it to the pattern space.

sed -i -e '/^<!--$/ {
    N; /\n<Connector port="8009" protocol="AJP\/1\.3" redirectPort="8443" \/>$/ {
        N; /\n-->$/ {
            s/^<!--\n//; s/\n-->$//
        }
    }
}' /myfile.xml

Arguably, if you need a command other than s, then you should switch away from sed and to awk or perl. Here's a slightly more flexible Perl snippet that copes with multiline comments in a more general way.

perl -i -pe '
    if (/<!--/) { $_ .= <> while !/-->/;
        s[<!--\n(<Connector port="8009" protocol="AJP/1\.3" redirectPort="8443" />)\n-->][$1];
    }' /myfile.xml

Solution 3:

Here's a description of the multiline commands in SED: http://docstore.mik.ua/orelly/unix/sedawk/ch06_01.htm

It's a pain in the butt. You may want to follow Eduardo's advice and use perl -i -p -e instead.


Solution 4:

  • /<\!--/ : matching string
  • :X : this is a label for branch command "b"
  • /-->/ : matching string
  • s@...@...@p : strip "<!--" , "-->" and print result
  • d : delete pattern space and start new cycle
  • N : if not match with /-->/ then append a line
  • bX : branch to :X label
  • p : just print a string that is not match with /<!--/

sed -rn '
/<!--/ {
    :X
    /-->/ {
        s@<!--\s*(<.+/>)\s*-->@\1@p
        d
    }
    N
    bX
};p'

and this second method is a simple copy & paste verbatim substitution for usual small sized text files ( need a shell script file )

#!/bin/bash

# copy & paste content that you want to substitute

AA=$( cat <<\EOF | sed -z -e 's#\([][^$*\.#]\)#\\\1#g' -e 's#\n#\\n#g'
<!--
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
-->
EOF
)

BB=$( cat <<\EOF | sed -z -e 's#\([&\#]\)#\\\1#g' -e 's#\n#\\n#g'
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
EOF
)

sed -z -i 's#'"${AA}"'#'"${BB}"'#g' *.xml   # apply to all *.xml files

Tags:

Linux

Bash

Sed