views:

201

answers:

5

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:

server.xml

<?xml version='1.0' encoding='UTF-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="80" protocol="HTTP/1.1"/>
    <Engine name="Catalina" defaultHost="localhost">
      <!-- BEGIN realm -->
        <sometags/>
        <sometags/>
      <!-- END realm -->
      <Host name="localhost" appBase="webapps"/>
    </Engine>
  </Service>
</Server>

realm.xml

<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
       resourceName="UserDatabase"/>

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.

+2  A: 
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`
TAIL_LINES=$(($TOTAL_LINES-$END_LINE))

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 :-)

Péter Török
It was not an exclusive requirement ;-)
rmarimon
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
@William Pursell - good point, fixed.
Péter Török
+2  A: 

you can use awk

awk 'FNR==NR{ _[++d]=$0;next}
/BEGIN realm/{
  print
  for(i=1;i<=d;i++){ print _[i] }
  f=1;next
}
/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.

ghostdog74
This seems like the right approach but it is very cryptic. Could you provide some clues into how this works?
rmarimon
How would change this so that it can do the replacement inplace like "sed -i"?
rmarimon
you just need to redirect to temp file and rename it back.
ghostdog74
+1  A: 

How about this little snippet I created:

sed -n \
  -e "1,/<\!-- BEGIN realm -->/ p" \
  -e"/<\!-- END realm -->/,$ p" \
  -e "/<\!-- BEGIN realm -->/ r realm.xml" \
  server.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!!!

rmarimon
what about `<sometags/>` ? your sed command doesn't replace `<sometags/>`.
ghostdog74
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
+3  A: 

Give this a try:

sed -ine '/<!-- BEGIN realm -->/ {p; r realm.xml' -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p' server.xml
Dennis Williamson
Whoa... it works. I'm trying to get a hang of the branching to really understand what's going on.
rmarimon
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
A: 

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
   H
   /BEGIN realm/i
   .
   /BEGIN realm/+1,/END realm/-1d
   .-1r realm.xml
   wq
EOF
yabt