Extrahieren Sie einen Attributwert aus XML

8

Mit Bash,

Datei:

<?xml version="1.0" encoding="UTF-8"?>
<blah>
    <blah1 path="er" name="andy" remote="origin" branch="master" tag="true" />
    <blah1 path="er/er1" name="Roger" remote="origin" branch="childbranch" tag="true" />
    <blah1 path="er/er2" name="Steven" remote="origin" branch="master" tag="true" />

</blah>

Ich habe folgendes versucht:

grep -i 'name="andy" remote="origin" branch=".*\"' <filename>

Aber es gibt die ganze Zeile zurück:

<blah1 path="er" name="andy" remote="origin" branch="master" tag="true" />

Ich möchte die Linie anhand der folgenden Punkte anpassen:

name="andy"

Ich möchte nur, dass es zurückkommt:

master
John
quelle

Antworten:

39

Verwenden Sie einen XML-Parser zum Parsen von XML-Daten. Mit Es wird nur eine XPath-Übung:

$ branch=$(xmlstarlet sel -t -v '//blah1[@name="andy"]/@branch' file.xml)
$ echo $branch
master
Glenn Jackman
quelle
10
Dies ist die bessere Antwort, da es auch dann weiter funktioniert, wenn jemand beschlossen hat, die Reihenfolge der Attribute zu ändern.
Hermann
4
@Hermann Oder ändert das Leerzeichen oder fügt ein anderes Element mit Attributen hinzu name="andy" branch="foo"oder ändert die Zeichenkodierung oder fügt ein Escape \"in das branchAttribut ein, oder oder oder ... Ich stimme zu; Verwenden Sie einfach einen XML-Parser!
Marcelm
4
branch=$(xmllint --xpath 'string(//blah1[@name="andy"]/@branch)' file.xml)ist der entsprechende Befehl mit xmllint.
David Conrad
3
@ DavidConrad machen das eine Antwort.
RonJohn
@ RonJohn Fertig. Ich habe mich auch entschieden, es in einen absoluten XPath zu ändern.
David Conrad
8

Verwenden Sie xmllint, um den Wert des Attributs mit XPath zu extrahieren:

xmllint --xpath 'string(/blah/blah1[@name="andy"]/@branch)' file.xml

Es ist besser, einen XML-Parser zum Verarbeiten von XML zu verwenden, da sich die Reihenfolge der Attribute ändern kann und Zeilenumbrüche eingefügt werden können, sodass sich die Namens- und Verzweigungsattribute in verschiedenen Zeilen der Datei befinden.

David Conrad
quelle
7

Mit grep:

grep -Pio 'name="andy".*branch="\K[^"]*' file
  • -P Aktivieren Sie reguläre Perl-Ausdrücke (PCRE).
  • -i Fall ignorieren
  • -o Drucken Sie nur übereinstimmende Teile

In der Regex \Kist dies ein Look mit einer Breite von Null, der mit dem Teil vor dem übereinstimmt \K, ihn jedoch nicht in die Übereinstimmung einbezieht .

Freddy
quelle
Ah, mit Grep habe ich versucht, dies zu tun, aber ich denke, mein Wissen war sehr begrenzt und ich wurde immer frustriert: $
John
Wunderbare Lösung, ich lerne jeden Tag.
Edward
4
Das Parsen von XML mit grep ist problematisch. Was ist, wenn sich die Reihenfolge der Attribute ändert? Was ist, wenn es ein anderes (Nicht- blah1) Element mit ähnlichen Attributen gibt? Was ist, wenn der Filialname enthält \"? Auch warum -i? Bei XML-Element- und Attributnamen wird zwischen Groß- und Kleinschreibung unterschieden. All diese Dinge sind Fehler, die darauf warten, irgendwann in der Zukunft aufzutauchen. Ich empfehle, das richtige Werkzeug für den Job zu verwenden. ein XML-Parser.
Marcelm
Das -istammt aus OP und kann nützlich sein, um mit den Attributwerten umzugehen (Roger, Steven). Wenn der Filialname einen hatte \", sollte er mit maskiert worden sein \&quot;. Ja, Sie haben Recht, XML kann sich ändern, Zeilenumbrüche usw. aufweisen. Ein XML-Parser ist definitiv die bessere Antwort, aber OP hat darum gebeten, grepund es könnte sein, dass er weiß, was er tut.
Freddy
3

Verwenden von awk:

awk '/name="andy"/{ for (i=1;i<=NF;i++) { if ($i ~ "branch=") { sub(/branch=/, ""); gsub(/"/, ""); print $i } } }' input

Dadurch wird eine Zeile gefunden, die name="andy"jedes Feld in dieser Zeile enthält, und anschließend durchlaufen. Wenn das Feld enthält, werden branch=wir branch=alle doppelten Anführungszeichen entfernen und den Rest des Feldes drucken.

sub(/branch=/, "")sucht nach einem Match von branch=und ersetzt es durch ""(nichts)

gsub ist ähnlich, außer dass es global ersetzt wird (alle Vorkommen anstelle nur des ersten Vorkommens).

jesse_b
quelle
Vielen Dank, ich werde google, um sub und gsub zu verstehen
John
Ich wünschte, ich könnte das bewerten, aber eine andere Antwort ist besser, wie Sie erwähnt haben.
John
Dies ist gut, funktioniert aber nur, wenn sich der Zweig in derselben Zeile mit dem Namen befindet.
David Conrad
@ DavidConrad: Ja, das ist die Voraussetzung. Wenn Sie bemerken, befindet sich der Zweig in jeder Zeile, aber OP möchte nur den Wert des Zweigs zurückgeben, der sich in derselben Zeile wie der Name befindet.
Jesse_b
Das ist jedoch nicht genau die Voraussetzung , so sieht diese Datei aus. XML erlaubt Leerzeichen. Wenn Sie also die Zeilen in Leerzeichen unterbrechen, funktioniert dies immer noch mit der am höchsten bewerteten Antwort, aber mit awk. Es ist eine Einschränkung, die Menschen, die diese Lösung verwenden, beachten sollten. Das heißt, dies ist eine gute, schnelle und schmutzige Lösung, und ich habe Sie positiv bewertet.
David Conrad
1

Ich denke das funktioniert:

$ grep -i 'name="andy" remote="origin" branch=".*\"' <filename> | awk -F' ' '{print $5}' | sed -E 's/branch=\"(.*)\"/\1/'
master

Das awkTeil stellt sicher, dass nur branch="master"zurückgegeben wird. Das sedTeil gibt mit einer Referenz zurück, was zwischen den doppelten Anführungszeichen steht (das \1stimmt mit dem Teil zwischen den Klammern überein).

Jetzt weiß ich, dass es hier draußen eine Menge Leute gibt, die viel mehr über die Kunst wissen, die awk und sed ist, also bin ich auf Kritik vorbereitet :-)

Edward
quelle
Aber ich übergebe den Aktengedanken: $ Vielen Dank für die Antwort, ich habe nicht daran gedacht, awk zu verwenden. Ich möchte nicht jede Zeile lesen, ich möchte irgendwie die ganze Datei lesen und das tun? Nicht möglich?
John
Bearbeiten Sie meine Antwort, um Ihnen zu zeigen, wie Sie sie durchleiten können.
Edward
Dies funktioniert, aber wie jede Lösung, die XML nicht als XML behandelt, funktioniert es nicht mehr, wenn sich die Reihenfolge der Attribute ändert oder Zeilenumbrüche eingefügt werden.
David Conrad
0

Wenn Sie auf Ihrem Computer keinen Zugriff auf xmllint oder xmlstarlet haben. Stellen Sie sicher, dass Sie Ihre XML in eine Zeile umwandeln, bevor Sie grep wie dieses verwenden

cat <filename> | tr -d '\n'

Jetzt sind Sie sicher, dass Tags nicht in separaten Zeilen aufgeteilt werden

| grep -Eo  "<blah1[>\ ][^<]+name=\"andy\"[^>]+."

wird ausgeschnitten (wie in xpath / blah1 [@ name = "andy"])

<blah1 path="er" name="andy" remote="origin" branch="master" tag="true" />

jetzt

| grep  -oP "(?<=branch\=\")[^\"]*"

wird zurückkehren (wie in xpath / @ branch)

Meister

alle zusammen

cat <filename> | tr -d '\n'| grep -Eo  "<blah1[>\ ][^<]+name=\"andy\"[^>]+." | grep  -oP "(?<=branch\=\")[^\"]*"
AnJo
quelle