Заменить блок текста с разделителями в файле содержимым другого файла

Мне нужно написать простой скрипт для замены блока текста в файле конфигурации содержимым другого файла.

Предположим, что у вас есть следующие упрощенные файлы:

server.xml

<?xml version='1.0' encoding='UTF-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"/>

Я хочу запустить скрипт и realm.xml заменить содержимое между строками <!-- BEGIN realm --> и <!-- END realm -->. Если realm.xml изменяется, то всякий раз, когда сценарий запускается снова, он снова заменяет строки новым содержимым realm.xml. Это предназначено для запуска в /etc/init.d/tomcat при запуске службы на нескольких установках, в которых область будет отличаться.

Я не совсем уверен, как я могу сделать это просто с помощью awk или sed.


person Ricardo Marimon    schedule 23.04.2010    source источник


Ответы (7)


Попробуйте это:

sed -i -ne '/<!-- BEGIN realm -->/ {p; r realm.xml' -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p' server.xml
person Dennis Williamson    schedule 23.04.2010
comment
Вау... работает. Я пытаюсь разобраться с ветвлением, чтобы действительно понять, что происходит. - person Ricardo Marimon; 23.04.2010
comment
ba переходит к метке a в фигурных скобках, связанных с тестом для BEGIN, а b переходит к концу, когда найден END, поскольку он находится в наборе фигурных скобок, связанных с этим тестом. Это вроде как if /BEGIN/ then read file; while not /END/ do skip line. - person Dennis Williamson; 23.04.2010
comment
Я получаю синтаксическую ошибку: sed: -e expression #1, char 39: unexpected }'` - person Steve Bennett; 16.02.2014
comment
@SteveBennett: -i должен стоять отдельно. В свернутом виде (как в -ine) он видит ne как суффикс файла резервной копии и не видит первое предложение -e, вызвавшее ошибку. Я исправил свой ответ. - person Dennis Williamson; 16.02.2014
comment
Работает как шарм, просто используйте двойные кавычки, если у вас есть переменная, содержащая имя файла с текстом, который нужно прочитать, поскольку одинарные кавычки отключают расширение оболочки. - person a1an; 28.07.2016
comment
Деннис Уильямсон, предложенная строка (ГОДА СТАРОЙ), я знаю, работает как удовольствие, использую ее сама, НО можете ли вы предложить, как я могу сделать это только для первого знакомства с /BEGIN/, поскольку в моем файле их несколько, и я хочу только первый поменял. Огромное спасибо - person ; 17.08.2017
comment
@gcclinux: в GNU sed: 0,/BEGIN/ {...} должно соответствовать только первому вхождению BEGIN. - person Dennis Williamson; 23.08.2017
comment
Любые советы о том, как заменить блок в файле (разделенный такими комментариями) содержимым переменной? Или мне лучше просто поместить его во временный файл? - person Adam; 03.07.2018
comment
@Adam: Попробуйте что-то вроде этого: sed -ne "/<\!-- BEGIN realm -->/ {s/.*/&\n$var/p" -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p'. Первый набор кавычек изменен с одинарного на двойной. Команда s (подстановка) заменяет команду r (чтение файла). & (вставить совпадение) и \n в подстановке заменяют исходную команду p (печать), которая становится флагом команды s. $var представляет вашу переменную, которая расширяется из-за двойных кавычек. Обратите внимание, что первый восклицательный знак необходимо экранировать, если история Bash активна из-за двойных кавычек. - person Dennis Williamson; 03.07.2018

вы можете использовать авк

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 передается awk в качестве первого файла. FNR==NR означает получение записей первого переданного файла и их сохранение в переменной _. awk обработает следующий файл, как только FNR!=NR. если awk находит /BEGIN realm/, напечатайте строку BEGIN realm, а затем напечатайте то, что хранится в _. При установке флага (f) в 1 остальные строки после BEGIN realm не будут напечатаны до тех пор, пока не будет обнаружено /END realm/.

person ghostdog74    schedule 23.04.2010
comment
Это кажется правильным подходом, но он очень загадочен. Не могли бы вы дать некоторые подсказки о том, как это работает? - person Ricardo Marimon; 23.04.2010
comment
Как бы изменить это, чтобы он мог выполнять замену на месте, например sed -i? - person Ricardo Marimon; 23.04.2010
comment
вам просто нужно перенаправить на временный файл и переименовать его обратно. - person ghostdog74; 23.04.2010

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

(Хорошо, это не использует awk или sed... Я предположил, что это не было исключительным требованием :-)

person Péter Török    schedule 23.04.2010
comment
Это не было исключительным требованием ;-) - person Ricardo Marimon; 23.04.2010
comment
Это работает? TOTAL_LINES будет иметь значение, включающее строку server.xml в большинстве версий wc, поэтому я подозреваю, что арифметика не сработает. - person William Pursell; 26.04.2010

Как насчет этого небольшого фрагмента, который я создал:

sed -n \
  -e "1,/<\!-- BEGIN realm -->/ p" \
  -e"/<\!-- END realm -->/,$ p" \
  -e "/<\!-- BEGIN realm -->/ r realm.xml" \
  server.xml

Первые команды печатают строки до <!- BEGIN realm -->, вторая команда печатает строку, начинающуюся с <!-- END realm -->, а третьи команды добавляют текст в файл «realm.xml». Если бы я только мог упростить удаление линий между <!- BEGIN realm --> и <!-- END realm -->, не удаляя линии маркеров, это было бы так же просто, как и получается. И это можно сделать inplace с помощью sed!!!

person Ricardo Marimon    schedule 23.04.2010
comment
как насчет <sometags/> ? ваша команда sed не заменяет <sometags/>. - person ghostdog74; 23.04.2010
comment
Когда я запускаю его на своей Linux-машине, это происходит. Более того, если вы запустите команду без последнего скрипта (-e), она выдаст server.xml без всех <sometags/>. - person Ricardo Marimon; 23.04.2010
comment
У меня не работает на Ubuntu Precise. Вставляет текст, но не удаляет ‹sometags/›... - person Steve Bennett; 16.02.2014

Вы также можете использовать команду ed (см. 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
person yabt    schedule 23.04.2010

Я столкнулся с этой же потребностью (отсюда и нашел этот вопрос). После слишком долгого возни с sed и awk я в конце концов понял, что нет ничего плохого в использовании современного, читаемого, понятного, широко доступного языка, такого как 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

Я думаю, что у sed и awk был свой день. Когда-то они были полезны, но в наши дни мало кто может читать или писать без документальной помощи.

(Источник: интернет)

person Steve Bennett    schedule 16.02.2014

Мне не удалось заставить решение Денниса легко работать на OS X (его BSD sed немного отличается). Я нашел это другое решение, которое смог заставить работать как в Linux, так и в OS X (у меня смешанная среда). Исходная версия на superuser.com работает только в Linux, здесь я это исправил:

lead='^<!-- BEGIN realm -->$'
tail='^<!-- END realm -->'
sed  -e '/'"$lead"'/,/'"$tail"'/{ /'"$lead"'/{p; r realm.xml' -e' }; /'"$tail"'/p; d;} '  server.xml

Вот версия кода Денниса, которая также работает в OS X (с использованием нескольких строк):

sed -ne '/'"$lead"'/ {
 p
 r realm.xml
 :a
 n 
 /'"$tail"'/ {
  p
  b
 } 
 ba
 }
p' server.xml

Оба этих кода выводят вывод на стандартный вывод. Используйте перенаправление или, чтобы заменить встроенный файл, добавьте опцию '-i' (в linux) или '-i ""' (в BSD/OS X).

person marco    schedule 23.05.2014