Take the 2-minute tour ×

I need to write a simple script to replace a block of text in a configuration file with the contents of another file.

Let's assume with have the following simplified files:


  <Service name="Catalina">
    <Connector port="80" protocol="HTTP/1.1"/>
      <!-- BEGIN realm -->
      <!-- END realm -->
      <Host name="localhost" appBase="webapps"/>


<Realm className="org.apache.catalina.realm.UserDatabaseRealm"

I want to run a script and have realm.xml replace the contents between the <!-- BEGIN realm --> and <!-- END realm --> lines. If realm.xml changes then whenever the script is run again it will replace the lines again with the new contents of realm.xml. This is intended to be run in /etc/init.d/tomcat on startup of the service on multiple installations on which the realm is going to be different.

I'm not so sure how can I do this simply with awk or sed.

share|improve this question
add comment

7 Answers

up vote 6 down vote accepted

Give this a try:

sed -i -ne '/<!-- BEGIN realm -->/ {p; r realm.xml' -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p' server.xml
share|improve this answer
Whoa... it works. I'm trying to get a hang of the branching to really understand what's going on. –  rmarimon Apr 23 '10 at 17:52
The ba branches to label "a" within the braces associated with the test for "BEGIN" and the b branches to the end when "END" is found since it's in a set of braces associated with that test. It's kind of like if /BEGIN/ then read file; while not /END/ do skip line. –  Dennis Williamson Apr 23 '10 at 18:05
I get a syntax error with this: sed: -e expression #1, char 39: unexpected }'` –  Steve Bennett Feb 16 at 6:41
@SteveBennett: The -i needs to stand alone. When collapsed (as in -ine), it sees the ne as the suffix of a backup file and failed to see the first -e clause which caused the error. I have corrected my answer. –  Dennis Williamson Feb 16 at 10:58
add comment
TOTAL_LINES=`cat server.xml | wc -l`
BEGIN_LINE=`grep -n -e '<!-- BEGIN realm -->' server.xml | cut -d : -f 1`
END_LINE=`grep -n -e '<!-- END realm -->' server.xml | cut -d : -f 1`

head -n $BEGIN_LINE server.xml > server2.xml
cat realm.xml > server2.xml
tail -n $TAIL_LINES server.xml > server2.xml

(OK, this does not use awk or sed... I assumed that was not an exclusive requirement :-)

share|improve this answer
It was not an exclusive requirement ;-) –  rmarimon Apr 23 '10 at 15:32
Does this work? TOTAL_LINES will have a value that includes the string "server.xml" in most versions of wc, so I suspect the arithmetic will fail. –  William Pursell Apr 26 '10 at 6:03
@William Pursell - good point, fixed. –  Péter Török Apr 26 '10 at 7:45
add comment

you can use awk

awk 'FNR==NR{ _[++d]=$0;next}
/BEGIN realm/{
  for(i=1;i<=d;i++){ print _[i] }
/END realm/{f=0}!f' realm.xml server.xml > temp && mv temp server.xml

realm.xml is passed to awk as the first file. FNR==NR means getting the records of the first file passed in and store to variable _. awk will process the next file once FNR!=NR. if awk finds /BEGIN realm/, print the BEGIN realm line, then print what is stored in _. By setting a flag (f) to 1, the rest of the lines after BEGIN realm will not be printed until /END realm/ is detected.

share|improve this answer
This seems like the right approach but it is very cryptic. Could you provide some clues into how this works? –  rmarimon Apr 23 '10 at 15:35
How would change this so that it can do the replacement inplace like "sed -i"? –  rmarimon Apr 23 '10 at 15:42
you just need to redirect to temp file and rename it back. –  ghostdog74 Apr 23 '10 at 16:05
add comment

How about this little snippet I created:

sed -n \
  -e "1,/<\!-- BEGIN realm -->/ p" \
  -e"/<\!-- END realm -->/,$ p" \
  -e "/<\!-- BEGIN realm -->/ r realm.xml" \

The first commands prints the lines up to <!- BEGIN realm --> the second command prints the line starting at <!-- END realm --> and the third commands append the text in the file 'realm.xml'. If only I could simplify the removing of the lines between <!- BEGIN realm --> and <!-- END realm --> without removing the marker lines it would as simple as it gets. And it can be done inplace with sed!!!

share|improve this answer
what about <sometags/> ? your sed command doesn't replace <sometags/>. –  ghostdog74 Apr 23 '10 at 16:53
When I run it in my linux machine it does. Moreover, if you run the command without the last script (-e) it gives the server.xml without all the <sometags/>. –  rmarimon Apr 23 '10 at 17:25
Doesn't work for me on Ubuntu Precise. Inserts text but doesn't remove the <sometags/>... –  Steve Bennett Feb 16 at 6:46
add comment

You may also use the ed command (cf. http://wiki.bash-hackers.org/howto/edit-ed ):

cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s server.xml
   /BEGIN realm/i
   /BEGIN realm/+1,/END realm/-1d
   .-1r realm.xml
share|improve this answer
add comment

I ran into this same need (hence finding this question). After messing around with sed and awk for far too long, I eventually realised there's nothing wrong with using a modern, readable, understandable, widely available language like Python:

    python <<EOF
    import os, sys, re
    fname = 'server.xml'
    os.rename(fname, fname + '.orig')
    with open(fname + '.orig', 'r') as fin, open(fname, 'w') as fout:
        data = fin.read()

        data = re.sub(r'(<!-- BEGIN realm -->).*?(<!-- END realm -->)', 
          r'\1\n' +
          'insert whatever you want here\n' + 
          r'\2\n', data, flags=re.DOTALL)

I think sed and awk have had their day. They were useful once upon a time, but very few people can read or write either without documentary assistance these days.

(Source: the internet)

share|improve this answer
add comment

I was unable to get Dennis solution easily working on OS X (its BSD sed is slightly different). I found this other solution that I was able to make work on both Linux and OS X (I have a mixed environment). The original version on superuser.com works only on Linux, here I fixed it:

lead='^<!-- BEGIN realm -->$'
tail='^<!-- END realm -->'
sed  -e '/'"$lead"'/,/'"$tail"'/{ /'"$lead"'/{p; r realm.xml' -e' }; /'"$tail"'/p; d;} '  server.xml

Here a version of Dennis code that works also on OS X (using multiple lines):

sed -ne '/'"$lead"'/ {
 r realm.xml
 /'"$tail"'/ {
p' server.xml

Both these codes print the output on stdout. Use redirection or, to substitute the file inline, add the option '-i' (on linux) or '-i ""' (on BSD/OS X).

share|improve this answer
add comment

Your Answer


