Використання sed для пошуку та заміни складного рядка (бажано з регулярним виразом)


84

У мене є файл із таким вмістом:

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

і мені потрібно зробити сценарій, який змінює "ім'я" в першому рядку на "щось", "пароль" у другому рядку на "щосьельси", а "ім'я" в третьому рядку на "щось інше". Я не можу покластися на порядок їх виникнення у файлі, тому я не можу просто замінити перше виникнення "name" на "щось", а друге виникнення "name" на "somethingdifferent". Мені фактично потрібно здійснити пошук навколишніх рядків, щоб переконатися, що я знаходжу та замінюю правильну річ.

Поки я намагався цю команду знайти та замінити перше виникнення "ім'я":

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

однак це не працює, тому я думаю, що деяким із цих персонажів може знадобитися втеча тощо.

В ідеалі, я б хотів, щоб я міг використовувати регулярний вираз, щоб просто збігатися з двома подіями "ім'я користувача" та замінити лише "ім'я". Щось подібне, але з sed:

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

і замініть вміст у дужках на "щось".

Чи можливо це?


2
Зауважте лише, що майже будь-яке рішення, що базується на регулярній основі, якщо не буде надзвичайно надуманим, ризикує порушити будь-який час зміни формату введення. Regexps - це поганий вибір для роботи з XML, SGML або похідними (що мені здається).
CVn

Схвалено! Розглянемо, наприклад, використання XQuery: w3schools.com/xquery/default.asp . Це стандарт W3C для отримання та маніпулювання вмістом XML.
lgeorget

Відповіді:


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

Я думаю, це те, що ви шукаєте.

Пояснення:

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

20
+1 для опції -E
slackmart

4
він залишає після себе резервний файл із назвою (original name) + "-E".
Сардж Борщ

4
На OSX я отримую 'sed: 1: "s / (<ім'я користувача>. +) Ім'я (. + ...": \ 1 не визначено в RE'. Я вставлю точний приклад з цього питання у файл. Тоді Я запустив команду з цієї відповіді на цей файл. Можливо, у OSX є інший синтаксис?
deweydb

1
Версія gnu sed підтримує параметр "-E", але не є офіційною. Це навіть не згадується на сторінці сторінки. Якщо ви хочете використовувати розширений регулярний вираз, вам доведеться використовувати параметр "-r".
Ikem Krueger

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/До sрозповідає СЕД для роботи тільки в рядках , що містять рядок «ім'я користувача».


1
Елегантний, ефективний та ідеально підходить для корпусу. +1
lgeorget

6

Якщо sedце не важка вимога, краще замість цього скористайтеся спеціальним інструментом.

Якщо у вашому файлі діє XML (не лише 3 теги, що шукають 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.