awk/sed: 在特定块号的最后一行之前插入文件内容

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

给定两个文件,第一个是 Apache 配置文件:

$ 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>

第二个文件包含应添加到一个(可变)特定 VirtualHost 块末尾的行:

$ cat inserted.txt
inserted line 1
 inserted line 2

结果应如下所示:

$ 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>

我尝试使用以下 sed 的一些变体,但没有成功:

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

我不知道如何 select 只有我需要插入文件的一个 VirtualHost 块,因为我必须使用 FreeBSD sed(或 awk),所以我也遇到了以前的错误sed 命令:

$ 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'

使用 GNU sed 我得到了这个输出:

$ 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

因为我想了解我的错误并从中吸取教训,所以我更喜欢有一些解释的答案,甚至可能有一些指向 rtfm 的链接,谢谢。

添加于 2016-10-16

伪代码:

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

${n-th} 收集者:

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

输出是我要扩展 FILE_2

的 BLOCK 的编号

请只使用非 GNU 版本,因为我使用的是 FreeBSD,谢谢。

awk 救援!

需要多字符记录分隔符,gawk

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

完整读取第一个文件并赋值给变量insert,同时在第二条记录的末尾迭代第二个文件,在记录内容后打印变量。

纯文本的另一个版本awk

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

在 GNU sed(和 BusyBox sed)中 file/label/text 在 abc、[=17 之后=]、rtw: 命令可以用分号分隔,而在其他版本的 sed 中,file/label/text 只能用换行符分隔。

该行为意味着不是定义标签 a,而是第一个字符串定义标签
a;n;/^<\/VirtualHost/\!ba;r inserted.txt,和右大括号分开使用-e一样,脚本必须在标签和分支之后分开。
(另外,! 不能转义)

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

或者,脚本可以跨越多行:

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

请注意,这种拆分在必须转义换行符的情况下可能不起作用;例如,当使用 aci 命令时。

鉴于:

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

你可以这样做:

$ awk 'BEGIN{fc=""} FNR==NR{fc=fc [=11=] "\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