Używanie sed do znajdowania i zastępowania złożonego łańcucha (najlepiej wyrażeniem regularnym)

84

Mam plik o następującej treści:

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

i muszę stworzyć skrypt, który zmieni „imię” w pierwszym wierszu na „coś”, „hasło” w drugim wierszu na „coś”, a „imię” w trzecim wierszu na „coś innego”. Nie mogę polegać na kolejności występowania w pliku, więc nie mogę po prostu zastąpić pierwszego wystąpienia „nazwy” słowem „coś”, a drugiego wystąpienia „nazwy” słowem „coś innego”. Właściwie muszę wyszukać otaczające ciągi, aby upewnić się, że znajduję i zastępuję poprawną rzecz.

Do tej pory próbowałem tego polecenia, aby znaleźć i zastąpić pierwsze wystąpienie „name”:

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

jednak to nie działa, więc myślę, że niektóre z tych postaci mogą wymagać ucieczki itp.

Idealnie, chciałbym móc użyć wyrażenia regularnego, aby po prostu dopasować dwa wystąpienia „nazwy użytkownika” i zastąpić tylko „imię”. Coś takiego, ale z sed:

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

i zamień zawartość w nawiasach na „coś”.

czy to możliwe?

Harry Muscle
źródło
2
Zauważ, że prawie każde rozwiązanie oparte na wyrażeniach regularnych, o ile nie jest wyjątkowo wymyślone, może zepsuć się przy każdej zmianie formatu wejściowego. Regexps są kiepskim wyborem do radzenia sobie z XML, SGML lub pochodnymi (co mi się wydaje).
CVn
Zatwierdzony! Rozważ użycie XQuery na przykład: w3schools.com/xquery/default.asp . Jest to standard W3C do pobierania i manipulowania treścią XML.
lgeorget

Odpowiedzi:

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

Myślę, że tego właśnie szukasz.

Wyjaśnienie:

  • nawiasy w pierwszej części definiują grupy (w rzeczywistości łańcuchy), które można ponownie wykorzystać w drugiej części
  • \1, \2itp. w drugiej części są odniesieniami do i-tej grupy uchwyconej w pierwszej części (numeracja zaczyna się od 1)
  • -Ewłącza rozszerzone wyrażenia regularne (potrzebne +i grupujące).
lgeorget
źródło
20
+1 za opcję -E
slackmart
4
pozostawia plik kopii zapasowej o nazwie (original name) + "-E".
Sarge Barszcz
4
W OSX dostaję „sed: 1:” s / (<nazwa użytkownika>. +) Nazwa (. + ... ”: \ 1 nie zdefiniowana w RE”. Wkleiłem dokładny przykład z tego pytania do pliku. Następnie W tym pliku uruchomiłem polecenie z tej odpowiedzi. Może OSX ma inną składnię?
deweydb
1
Wersja sed gnu obsługuje parametr „-E”, ale nie jest oficjalna. Nie jest nawet wspomniany na stronie podręcznika. Jeśli chcesz użyć rozszerzonego wyrażenia regularnego, musisz zamiast tego użyć parametru „-r”.
Ikem Krueger,
3
@deweydb Zgodnie z tą odpowiedzią powinieneś użyć \(i \)zamiast (i ).
Zhang Buzz,
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/Przed snakazuje sed tylko pracy na liniach zawierających ciąg „nazwę użytkownika”.

zła
źródło
1
Elegancki, wydajny i doskonale dopasowany do etui. +1
lgeorget
6

Jeśli sednie jest to trudne wymaganie, lepiej użyć dedykowanego narzędzia.

Jeśli plik ma poprawny kod XML (nie tylko te 3 znaczniki wyglądające na XML), możesz użyć XMLStarlet :

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

Powyższe będzie również działać w sytuacjach, które trudno byłoby rozwiązać za pomocą wyrażeń regularnych:

  • Może zastąpić wartości znaczników bez określania ich bieżących wartości.
  • Może zastąpić wartości, nawet jeśli są one tylko znakami ucieczki i nie są zawarte w CDATA.
  • Może zastąpić wartości, nawet jeśli tagi mają atrybuty.
  • Może łatwo zastąpić tylko wystąpienia tagów, jeśli jest ich wiele o tej samej nazwie.
  • Może sformatować zmodyfikowany XML poprzez wcięcie.

Krótka prezentacja powyższych:

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>
człowiek w pracy
źródło
3

Musisz cytować \[.*^$/w części wyrażenia regularnego spolecenia oraz \&/w części zastępczej, a także nowe wiersze. Wyrażenie regularne jest podstawowym wyrażeniem regularnym , a ponadto musisz podać cudzysłów dla spolecenia.

Możesz wybrać inny separator, aby uniknąć konieczności cytowania /. Zamiast tego musisz zacytować ten znak, ale zwykle celem zmiany separatora jest wybranie takiego, który nie występuje ani w tekście, który ma zostać zastąpiony, ani w tekście zastępującym.

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

Możesz użyć grup, aby uniknąć powtarzania niektórych części w tekście zamiennym i dostosować warianty tych części.

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

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'
Gilles
źródło
3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Możesz po prostu użyć adresów jak w numerze poprzedzającym „s”, który wskazuje numer linii.

Również liczba na końcu mówi sedo zastąpieniu drugiego dopasowania zamiast pierwszego.

A. Wench
źródło
1

Aby zastąpić słowo „nazwa” słowem „coś”, użyj:

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

To zastąpi wszystkie wystąpienia określonego słowa.

Do tej pory wszystko jest wyprowadzane na standardowe wyjście, możesz użyć:

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

aby zapisać zmiany w innym pliku.

slackmart
źródło
0
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

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

aby zastąpić wartość w pliku właściwości

sed -i -r 's/MAIL\=(.+)/MAIL\[email protected]/' etc/service.properties 
alfiogang
źródło