awk/sed: Insert file content before last line of s

2019-07-22 05:02发布

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.

3条回答
放荡不羁爱自由
2楼-- · 2019-07-22 05:48

In GNU sed (and BusyBox sed) the file/label/text after the a, b, c, i, r, t, w, and : commands can be delimited by a semicolon, while in other versions of sed the file/label/text may only be delimited by a newline.

That behavior means that instead of defining the label a, the first string defines the label
a;n;/^<\/VirtualHost/\!ba;r inserted.txt, and like the separate use of -e for the closing brace, the script must be separated after both the label and the branch.
(also, the ! must not be escaped)

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

Alternatively, the script can span multiple lines:

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

Note that this splitting may not work in situations where the newline must be escaped; for example, when using the a, c, and i commands.

查看更多
Anthone
3楼-- · 2019-07-22 05:49

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
查看更多
一夜七次
4楼-- · 2019-07-22 05:50

Given:

$ cat f1.txt
line 1
line 2
line 3
INSERT HERE
line 4
line 5
$ cat f2.txt
INSERTED LINE 1
INSERTED LINE 2

You can do:

$ awk 'BEGIN{fc=""} FNR==NR{fc=fc $0 "\n";next} /^INSERT HERE/{printf "%s", fc; next} 1' f2.txt f1.txt
line 1
line 2
line 3
INSERTED LINE 1
INSERTED LINE 2
line 4
line 5
查看更多
登录 后发表回答