Verwenden von sed zum Suchen und Ersetzen komplexer Zeichenfolgen (vorzugsweise mit Regex)

84

Ich habe eine Datei mit folgendem Inhalt:

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

und ich muss ein Skript erstellen, das "name" in der ersten Zeile in "something", "password" in der zweiten Zeile in "somethingelse" und "name" in der dritten Zeile in "somethingdifferent" ändert. Ich kann mich nicht darauf verlassen, dass diese in der Datei in der richtigen Reihenfolge vorkommen. Daher kann ich das erste Vorkommen von "name" nicht einfach durch "something" und das zweite Vorkommen von "name" durch "somethingdifferent" ersetzen. Ich muss tatsächlich nach den umgebenden Zeichenfolgen suchen, um sicherzustellen, dass ich die richtige Sache finde und ersetze.

Bisher habe ich diesen Befehl ausprobiert, um das Vorkommen des ersten "Namens" zu finden und zu ersetzen:

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

Es funktioniert jedoch nicht, daher denke ich, dass einige dieser Zeichen möglicherweise entkommen müssen.

Im Idealfall würde ich gerne Regex verwenden, um nur die beiden Vorkommen "Benutzername" abzugleichen und nur den "Namen" zu ersetzen. So etwas aber mit sed:

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

und ersetzen Sie den Inhalt in den Klammern durch "etwas".

Ist das möglich?

Harry Muscle
quelle
2
Beachten Sie nur, dass so gut wie jede auf RegExp basierende Lösung, sofern sie nicht besonders ausgeklügelt ist, zu Problemen führen kann, wenn sich das Eingabeformat ändert. Regexps sind eine schlechte Wahl für den Umgang mit XML, SGML oder Derivaten (wie ich es sehe).
CVn
Genehmigt! Verwenden Sie beispielsweise XQuery: w3schools.com/xquery/default.asp . Dies ist der W3C-Standard zum Abrufen und Bearbeiten von XML-Inhalten.
Lgeorget

Antworten:

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

Das ist, denke ich, wonach Sie suchen.

Erläuterung:

  • Klammern im ersten Teil definieren Gruppen (Strings), die im zweiten Teil wiederverwendet werden können
  • \1, \2usw. im zweiten Teil sind Verweise auf die im ersten Teil erfasste i-te Gruppe (die Nummerierung beginnt mit 1)
  • -Eaktiviert erweiterte reguläre Ausdrücke (benötigt für +und Gruppierung).
lgeorget
quelle
21
+1 für die Option -E
Slackmart
4
es hinterlässt eine Sicherungsdatei mit dem Namen (original name) + "-E".
Sarge Borsch
4
Unter OSX bekomme ich 'sed: 1: "s / (<Benutzername>. +) Name (. + ...": \ 1 nicht in der RE definiert'. Ich habe das genaue Beispiel aus dieser Frage in eine Datei eingefügt Ich habe den Befehl von dieser Antwort auf diese Datei ausgeführt. Vielleicht hat OSX eine andere Syntax?
Deweydb
1
Die Gnu-Version von sed unterstützt den Parameter "-E", ist jedoch nicht offiziell. Es wird nicht einmal in der Manpage erwähnt. Wenn Sie den erweiterten regulären Ausdruck verwenden möchten, müssen Sie stattdessen den Parameter "-r" verwenden.
Ikem Krueger
3
@deweydb Nach dieser Antwort sollten Sie \(und \)anstelle von (und verwenden ).
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

Der Befehl /username/vor dem sBefehl soll nur für Zeilen verwendet werden, die die Zeichenfolge 'Benutzername' enthalten.

Übelsuppe
quelle
1
Elegant, effizient und perfekt auf den Koffer abgestimmt. +1
Lgeorget
6

Wenn dies sednicht unbedingt erforderlich ist, sollten Sie stattdessen ein spezielles Tool verwenden.

Wenn Ihre Datei gültiges XML ist (nicht nur diese drei XML-Tags), können Sie XMLStarlet verwenden :

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

Das Obige funktioniert auch in Situationen, die mit regulären Ausdrücken schwer zu lösen sind:

  • Kann die Werte der Tags ersetzen, ohne die aktuellen Werte anzugeben.
  • Kann die Werte ersetzen, auch wenn sie nur maskiert und nicht in CDATA enthalten sind.
  • Kann die Werte auch dann ersetzen, wenn die Tags Attribute haben.
  • Kann einfach nur das Auftreten von Tags ersetzen, wenn mehrere mit demselben Namen vorhanden sind.
  • Kann das geänderte XML durch Einrücken formatieren.

Kurze Demonstration der oben genannten:

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>
Mann bei der Arbeit
quelle
3

Sie müssen \[.*^$/im regulären Ausdrucksteil des sBefehls und \&/im Ersatzteil plus Zeilenumbrüche angeben. Der reguläre Ausdruck ist ein grundlegender regulärer Ausdruck . Außerdem müssen Sie den Begrenzer für den sBefehl in Anführungszeichen setzen .

Sie können ein anderes Trennzeichen auswählen, um Anführungszeichen zu vermeiden /. Sie müssen stattdessen dieses Zeichen in Anführungszeichen setzen. In der Regel müssen Sie jedoch ein Trennzeichen auswählen, das weder im zu ersetzenden Text noch im Ersatztext vorkommt.

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

Sie können Gruppen verwenden, um zu vermeiden, dass einige Teile im Ersetzungstext wiederholt werden, und Variationen an diesen Teilen berücksichtigen.

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
quelle
3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

Sie können einfach Adressen wie in der Zahl vor "s" verwenden, die die Zeilennummer angibt.

Auch die Zahl am Ende weist seddarauf hin, die zweite Übereinstimmung zu ersetzen, anstatt die erste Übereinstimmung zu ersetzen.

A. Wench
quelle
1

Um das Wort "name" durch das Wort "something" zu ersetzen, verwenden Sie:

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

Das wird alle Vorkommen des angegebenen Wortes ersetzen.

Bisher wird alles auf Standardausgabe ausgegeben. Sie können Folgendes verwenden:

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

um die Änderungen in einer anderen Datei zu speichern.

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

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

So ersetzen Sie den Wert in einer Eigenschaftendatei

sed -i -r 's/MAIL\=(.+)/MAIL\[email protected]/' etc/service.properties 
Alfiogang
quelle