SED replace across multiple lines
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
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
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.
-
/<\!--/
: 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