Replace delimited block of text in file with the contents of another file
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='U开发者_JS百科TF-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
.
Give this a try:
sed -i -ne '/<!-- BEGIN realm -->/ {p; r realm.xml' -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p' server.xml
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 :-)
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.
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!!!
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
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)
fout.write(data)
EOF
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)
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"'/ {
p
r realm.xml
:a
n
/'"$tail"'/ {
p
b
}
ba
}
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).
精彩评论