Joe User Joe User - 1 month ago 11
Bash Question

awk/sed: Insert file content before last line of specific block number

Given are two files, the first is an Apache config file:

$ cat vhosts-ssl.conf
<VirtualHost *:443>
vhost 1
foobar 1
foobar 2
barfoo 1
barfoo 2
</VirtualHost>

<VirtualHost *:443>
vhost 2
foobar 2
barfoo 1
foobar 1
barfoo 2
</VirtualHost>
<VirtualHost *:443>
vhost 3
foobar 1

barfoo 1
foobar 2

barfoo 2
</VirtualHost>

<VirtualHost *:443>

vhost 4
foobar 1
foobar 2

barfoo 1
barfoo 2

</VirtualHost>


And the second file contains lines that should be added to the end of one (variable) specific VirtualHost block:

$ cat inserted.txt
inserted line 1
inserted line 2


The result should look like this:

$ cat vhosts-ssl.conf
<VirtualHost *:443>
vhost 1
foobar 1
foobar 2
barfoo 1
barfoo 2
</VirtualHost>

<VirtualHost *:443>
vhost 2
foobar 2
barfoo 1
foobar 1
barfoo 2
inserted line 1
inserted line 2
</VirtualHost>
<VirtualHost *:443>
vhost 3
foobar 1

barfoo 1
foobar 2

barfoo 2
</VirtualHost>

<VirtualHost *:443>

vhost 4
foobar 1
foobar 2

barfoo 1
barfoo 2

</VirtualHost>


I tried it with some variations of the following sed but that didn't do the trick:

$ sed -e '/^<VirtualHost/{:a;n;/^<\/VirtualHost/\!ba;r inserted.txt' -e '}' vhosts-ssl.conf


I can't figure out how to select only the one VirtualHost block i need to insert the file to and since i've to use FreeBSD sed (or awk) i also get this error with previous sed command:

$ sed -e '/^<VirtualHost/{:a;n;/^<\/VirtualHost/\!ba;r inserted.txt' -e '}' vhosts-ssl.conf
sed: 2: "}
": unused label 'a;n;/^<\/VirtualHost/!ba;r inserted.txt'


With GNU sed i get the this output:

$ gsed -e '/^<VirtualHost/{:a;n;/^<\/VirtualHost/\!ba;r inserted.txt' -e '}' vhosts-ssl.conf
<VirtualHost *:443>
vhost 1
foobar 1
foobar 2
barfoo 1
barfoo 2
</VirtualHost>
inserted line 1
inserted line 2


<VirtualHost *:443>
vhost 2
foobar 2
barfoo 1
foobar 1
barfoo 2
</VirtualHost>
inserted line 1
inserted line 2

<VirtualHost *:443>
vhost 3
foobar 1

barfoo 1
foobar 2

barfoo 2
</VirtualHost>
inserted line 1
inserted line 2


<VirtualHost *:443>

vhost 4
foobar 1
foobar 2

barfoo 1
barfoo 2

</VirtualHost>
inserted line 1
inserted line 2


As i would like to understand my mistakes and lern from them, i would prefer answers with some explanation and maybe even some links to rtfm, thanks.

Added 2016-10-16

Pseudocode:

if BLOCK begins with /^<VirtualHost/
and ends with /^<\/VirtualHost/
and is the ${n-th} BLOCK
in FILE_1
then insert content of FILE_2
before last line of ${n-th} BLOCK
without touching rest of FILE_1
endif
save modified FILE_1


The ${n-th} is gathered by:

$ httpd -t -D DUMP_VHOSTS | \
grep -i "${SUBDOMAIN}.${DOMAIN}" | \
awk '/^[^\ ]*:443[\ ]*/ {print $3}' | \
sed -e 's|(\(.*\))|\1|' | \
cut -d: -f2


Output is a the number of the BLOCK i want to extend by FILE_2

And please only non-GNU versions as i'm on FreeBSD, thanks.

Answer

awk to the rescue!

requires multi-char record separator, supported by gawk

$ awk 'NR==FNR{insert=$0; next} 
  {print $0 (FNR==2?insert:"") RT}' RS='^$' insert.file RS="</VirtualHost>" file 

read the first file in complete and assign to the variable insert, while iterating the second file at the end of second record print the variable after the record contents.

Another version for plain awk

$ awk 'NR==FNR{insert=insert?insert ORS $0:$0; next} 
       /<\/VirtualHost>/ && ++c==2{print insert} 1' insert.file file
Comments