awk / sed: вставить содержимое файла перед последней строкой определенного номера блока

#shell #unix #awk #sed #freebsd

#оболочка #unix #awk #sed #freebsd

Вопрос:

Приведены два файла, первый — конфигурационный файл 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>
 

И второй файл содержит строки, которые должны быть добавлены в конец одного (переменного) конкретного блока виртуального хоста:

 $ 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
 

Я не могу понять, как выбрать только один блок 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-й} собирается с помощью:

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

Вывод — это номер БЛОКА, который я хочу расширить на FILE_2

И, пожалуйста, только версии, отличные от GNU, поскольку я на FreeBSD, спасибо.

Комментарии:

1. Я добавил некоторый псевдокод

Ответ №1:

awk на помощь!

требуется разделитель записей с несколькими символами, поддерживаемый gawk

 $ awk 'NR==FNR{insert=$0; next} 
  {print $0 (FNR==2?insert:"") RT}' RS='^

прочитайте первый файл полностью и присвоите переменной insert, при повторении второго файла в конце второй записи выведите переменную после содержимого записи.

Другая версия для простого  awk  

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

Комментарии:

1. @karakfa ... В последний раз, когда я видел твою репутацию, у тебя было 1500, что было год назад .. ты == неуклюжий !... хороший ответ ..lol

2. Хорошо, работает именно так, как мне нужно, но, как вы сказали, только с gawk.

3. Мне это нужно для FreeBSD awk.

4. Вторая версия делает это. Теперь я должен прочитать немного больше о awk, кажется, он более мощный, чем sed. Спасибо.

Ответ №2:

В GNU sed (и BusyBox sed ) файл / метка / текст после a , b , c , i , r , t , w , и : команды могут быть разделены точкой с запятой, в то время как в других версиях sed файл / метка / текст может быть разделен только новой строкой.

Такое поведение означает, что вместо определения метки 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
 

Обратите внимание, что это разделение может не сработать в ситуациях, когда необходимо экранировать новую строку; например, при использовании команд a , c , и i .

Комментарии:

1. Благодарим за объяснение различий в поведении метки.

Ответ №3:

Учитывая:

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

Комментарии:

1. Хорошо, но у меня нет ничего, что я мог бы использовать как "ВСТАВИТЬ ЗДЕСЬ", см. Мои опубликованные vhosts-ssl.conf и недавно добавленный псевдокод.

insert.file RS="</VirtualHost>" file
прочитайте первый файл полностью и присвоите переменной insert, при повторении второго файла в конце второй записи выведите переменную после содержимого записи.

Другая версия для простого awk


Комментарии:

1. @karakfa … В последний раз, когда я видел твою репутацию, у тебя было 1500, что было год назад .. ты == неуклюжий !… хороший ответ ..lol

2. Хорошо, работает именно так, как мне нужно, но, как вы сказали, только с gawk.

3. Мне это нужно для FreeBSD awk.

4. Вторая версия делает это. Теперь я должен прочитать немного больше о awk, кажется, он более мощный, чем sed. Спасибо.

Ответ №2:

В GNU sed (и BusyBox sed ) файл / метка / текст после a , b , c , i , r , t , w , и : команды могут быть разделены точкой с запятой, в то время как в других версиях sed файл / метка / текст может быть разделен только новой строкой.

Такое поведение означает, что вместо определения метки a первая строка определяет метку
a;n;/^</VirtualHost/!ba;r inserted.txt и, как и при отдельном использовании -e для закрывающей фигурной скобки, сценарий должен быть разделен как после метки, так и после ветки.
(кроме того, ! не должно быть экранировано)


Альтернативно, сценарий может занимать несколько строк:


Обратите внимание, что это разделение может не сработать в ситуациях, когда необходимо экранировать новую строку; например, при использовании команд a , c , и i .

Комментарии:

1. Благодарим за объяснение различий в поведении метки.

Ответ №3:

Учитывая:


Вы можете сделать:


Комментарии:

1. Хорошо, но у меня нет ничего, что я мог бы использовать как «ВСТАВИТЬ ЗДЕСЬ», см. Мои опубликованные vhosts-ssl.conf и недавно добавленный псевдокод.