Использование sed для поиска и замены сложной строки (желательно с регулярным выражением)


85

У меня есть файл со следующим содержанием:

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

и мне нужно сделать скрипт, который меняет «имя» в первой строке на «что-то», «пароль» во второй строке на «что-то другое», а «имя» в третьей строке на «что-то другое». Я не могу полагаться на порядок их появления в файле, поэтому я не могу просто заменить первое вхождение «name» на «что-то», а второе вхождение «name» на «что-то другое». Мне действительно нужно выполнить поиск окружающих строк, чтобы убедиться, что я нахожу и заменяю правильную вещь.

До сих пор я пробовал эту команду, чтобы найти и заменить первое вхождение «имя»:

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml

однако это не работает, поэтому я думаю, что некоторым из этих персонажей, возможно, понадобится убежать и т. д.

В идеале я хотел бы использовать регулярные выражения, чтобы просто сопоставить два вхождения "имя пользователя" и заменить только "имя". Как то так, но с sed:

<username>.+?(name).+?</username>

и заменить содержимое в скобках на «что-то».

Это возможно?


2
Просто отметьте, что практически любое решение на основе регулярных выражений, если оно не очень надумано, рискует сломаться при каждом изменении формата ввода. Регулярные выражения - плохой выбор для работы с XML, SGML или производными (что мне кажется).
CVn

Одобренный! Рассмотрите возможность использования XQuery, например: w3schools.com/xquery/default.asp . Это стандарт W3C для извлечения и манипулирования контентом XML.
lgeorget

Ответы:


158
sed -i -E "s/(<username>.+)name(.+<\/username>)/\1something\2/" file.xml

Это, я думаю, то, что вы ищете.

Объяснение:

  • круглые скобки в первой части определяют группы (на самом деле строки), которые могут быть повторно использованы во второй части
  • \1, \2и т. д. во второй части - ссылки на i-ю группу, захваченную в первой части (нумерация начинается с 1)
  • -Eвключает расширенные регулярные выражения (необходимые для +и группировки).

21
+1 за опцию -E
slackmart

4
он оставляет файл резервной копии с именем (original name) + "-E".
Сардж Борщ

4
В OSX я получаю 'sed: 1: "s / (<username>. +) Name (. + ...": \ 1 не определено в RE'. Я вставил точный пример из этого вопроса в файл. Затем я выполнил команду из этого ответа на этот файл. Может быть, OSX имеет другой синтаксис?
deweydb

1
Версия sed для GNU поддерживает параметр "-E", но не является официальной. Это даже не упомянуто на странице руководства. Если вы хотите использовать расширенное регулярное выражение, вместо этого вы должны использовать параметр "-r".
Икем Крюгер

3
@deweydb Согласно этому ответу , вы должны использовать \(и \)вместо (и ).
Чжан Базз

14
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

Значение /username/before sуказывает sed работать только со строками, содержащими строку username.


1
Элегантный, эффективный и идеально подходит для случая. +1
lgeorget

6

Если sedэто не сложное требование, лучше использовать специальный инструмент.

Если ваш файл является допустимым XML (а не только этими тремя XML-выглядящими тегами), то вы можете использовать XMLStarlet :

xml ed -P -O -L \
  -u '//username/text()' -v 'something' \
  -u '//password/text()' -v 'somethingelse' \
  -u '//dbname/text()' -v 'somethingdifferent' file.xml

Вышеуказанное также будет работать в ситуациях, которые трудно решить с помощью регулярных выражений:

  • Можно заменить значения тегов без указания их текущих значений.
  • Может заменить значения, даже если они просто экранированы и не включены в CDATA.
  • Может заменить значения, даже если у тегов есть атрибуты.
  • Можно легко заменить только вхождения тегов, если есть несколько с одним и тем же именем.
  • Можно отформатировать модифицированный XML, отступив его.

Краткая демонстрация вышесказанного:

bash-4.2$ cat file.xml
<sith>
<master>
<username><![CDATA[name]]></username>
</master>
<apprentice>
<username><![CDATA[name]]></username>
<password>password</password>
<dbname foo="bar"><![CDATA[name]]></dbname>
</apprentice>
</sith>

bash-4.2$ xml ed -O -u '//apprentice/username/text()' -v 'something' -u '//password/text()' -v 'somethingelse' -u '//dbname/text()' -v 'somethingdifferent' file.xml
<sith>
  <master>
    <username><![CDATA[name]]></username>
  </master>
  <apprentice>
    <username><![CDATA[something]]></username>
    <password>somethingelse</password>
    <dbname foo="bar"><![CDATA[somethingdifferent]]></dbname>
  </apprentice>
</sith>

3

Вы должны заключить \[.*^$/в кавычки в части регулярного выражения sкоманды и \&/в части замены, плюс новые строки. Регулярное выражение является базовым регулярным выражением , и, кроме того, вам нужно заключить в кавычки sкоманду.

Вы можете выбрать другой разделитель, чтобы избежать цитирования /. Вместо этого вам придется заключать в кавычки этот символ, но обычно смысл изменить разделитель, чтобы выбрать тот, который не встречается ни в тексте для замены, ни в тексте замены.

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'

Вы можете использовать группы, чтобы избежать повторения некоторых частей в тексте замены и учесть различия в этих частях.

sed -e 's~\(<username><!\[[A-Z]*\[\)name\(\]\]></username>\)~\1something\2~'

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'

3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Вы можете просто использовать адреса как в числе, предшествующем "s", которое указывает номер строки.

Кроме того, число в конце говорит, sedчтобы заменить второе совпадение вместо замены первого совпадения.


1

Для замены слова «имя» словом «что-то» используйте:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml

Это заменит все вхождения указанного слова.

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

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml > anotherfile.xml

сохранить изменения в другом файле.


0
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    -r, --regexp-extended
             use extended regular expressions in the script.

чтобы заменить значение в файле свойств

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.