xmlstarlet ist das Unix-CLI-Schweizermesser für XML — es kombiniert XPath-Abfrage, Massen-Edit, XSLT-Transformation, Schema-Validierung und Pretty-Print in einem einzigen Werkzeug. Wer regelmässig grössere Mengen XML-Dateien (TEI-Editionen, EAD-Findmittel, METS-Manifeste, OAI-PMH-Records) auf der Shell prozessiert, kommt an xmlstarlet kaum vorbei.

Das Programm lebt seit ~2002 als plattformunabhängige C-Anwendung um libxml2 und libxslt herum; es liefert verlässliche XPath 1.0- und XSLT-1.0-Unterstützung mit grosszügigen Optionen für Namespaces, Encoding und Output-Formatierung.

Installation

# macOS
brew install xmlstarlet

# Debian/Ubuntu
apt install xmlstarlet

# Aufruf entweder als `xmlstarlet` oder kürzer `xml`
xml --version

Auf den meisten Systemen ist sowohl xmlstarlet als auch das kürzere xml als Alias installiert.

Die Sub-Kommandos

KommandoAufgabe
selSelect — XPath-Abfrage, Resultate ausgeben
edEdit — Knoten einfügen, ändern, löschen, umbenennen
valValidate — gegen DTD, XSD oder RelaxNG
trTransform — XSLT 1.0 ausführen
foFormat — Pretty-Print, Encoding-Konversion
c14nCanonicalize — XML-Kanonisierung (XMLDsig-tauglich)
esc / unescXML-Entitäten escapen / unescapen
elemXML-Element-Generator
pyxKonversion zu/von PYX (Lex-/Linien-orientiertes Hilfsformat)
lsVerzeichnis als XML auflisten

In der Praxis sind sel, ed, val und tr die meistgenutzten.

sel — XPath-Abfragen

Werte oder Knoten aus einem XML-Dokument extrahieren.

# Alle persName-Texte ausgeben (ohne Namespace)
xmlstarlet sel -t -m '//persName' -v 'text()' -n datei.xml

# Mit TEI-Namespace
xmlstarlet sel -N t='http://www.tei-c.org/ns/1.0' \
  -t -m '//t:persName' -v 'text()' -n datei.tei.xml

# Attribut-Werte sammeln
xmlstarlet sel -t -v '//persName/@ref' -n datei.xml | sort -u

# Mehrere Spalten als TSV ausgeben
xmlstarlet sel -t \
  -m '//tei:person' \
  -v '@xml:id' -o $'\t' \
  -v 'tei:persName' -o $'\t' \
  -v 'tei:birth/@when' \
  -n \
  -N tei='http://www.tei-c.org/ns/1.0' \
  datei.xml

# Anzahl Elemente
xmlstarlet sel -t -v 'count(//letter)' datei.xml

# Bedingte Selektion (Personen ohne ref)
xmlstarlet sel -N t='http://www.tei-c.org/ns/1.0' \
  -t -m '//t:persName[not(@ref)]' \
  -v 'concat(., " (in ", ancestor::t:div[1]/@xml:id, ")")' -n \
  datei.tei.xml

Die wichtigsten -t (Template)-Bausteine:

OptionBedeutung
-m '<xpath>'Match — pro Treffer ausführen
-v '<xpath>'Value — Wert ausgeben
-c '<xpath>'Copy — kompletten Knoten ausgeben
-o 'text'Output — wörtlichen Text ausgeben
-nNewline
-i 'cond'If — bedingt
-N prefix=URINamespace binden

ed — Editieren in-place oder pipe

Knoten ändern, einfügen, löschen — ohne XSLT-Stylesheet, direkt auf der Shell.

# Attribut auf alle persName setzen, in-place
xmlstarlet ed --inplace \
  -u '//persName[not(@cert)]/@cert' -v 'high' \
  datei.xml

# Wenn das Attribut noch nicht existiert: erst inserten
xmlstarlet ed --inplace \
  -i '//persName[not(@cert)]' -t attr -n 'cert' -v 'high' \
  datei.xml

# Alle TODO-Kommentare löschen
xmlstarlet ed --inplace \
  -d '//note[@type="todo"]' \
  datei.xml

# Element umbenennen
xmlstarlet ed --inplace \
  -r '//placename' -v 'placeName' \
  datei.xml

# Knoten am Ende einfügen
xmlstarlet ed --inplace \
  -s '//body' -t elem -n 'div' -v 'Anhang' \
  datei.xml

# Mehrere Operationen verkettet
xmlstarlet ed \
  -d '//note[@type="todo"]' \
  -u '//persName[not(@cert)]/@cert' -v 'high' \
  datei.xml > datei-bereinigt.xml

-d (delete), -u (update), -i (insert), -a (append), -s (subnode), -r (rename), -m (move). Die Kombination mit XPath erlaubt sehr präzise Massen-Eingriffe — was in einem Editor 200 manuelle Klicks wären, ist hier ein Einzeiler.

val — Schema-Validierung

# Gegen XSD validieren
xmlstarlet val --xsd schema.xsd datei.xml

# Gegen DTD (DOCTYPE-Eintrag in der Datei oder explizit)
xmlstarlet val --dtd model.dtd datei.xml

# Gegen RelaxNG (relevant für TEI)
xmlstarlet val --relaxng tei_all.rng datei.xml

# Mehrere Dateien batch
xmlstarlet val --relaxng tei_all.rng *.xml
# → Output: "datei.xml - valid" oder Fehlermeldung mit Zeilennummer

Für TEI-Validierung gegen das Customization-Schema ist xmlstarlet val --relaxng <schema>.rng der schnellste Weg im CI/CD-Setup. Saubere Exit-Codes (0 = valid, >0 = invalid) machen es shell-script-tauglich.

tr — XSLT-Transformation

# Stylesheet anwenden
xmlstarlet tr stylesheet.xsl datei.xml > output.html

# Mit Parametern
xmlstarlet tr stylesheet.xsl -s lang=de datei.xml

# In Pipeline (XSLT-Output direkt weiterverarbeiten)
xmlstarlet tr to-csv.xsl edition.xml | xsv stats

xmlstarlet nutzt libxslt; das ist XSLT 1.0 mit EXSLT-Erweiterungen. Für XSLT 2.0/3.0 braucht es Saxon — dort steigt man typisch aus xmlstarlet tr aus.

fo — Pretty-Print und Encoding

# Pretty-Print
xmlstarlet fo datei.xml > formatted.xml

# Pretty-Print mit Tab-Einrückung statt Leerzeichen
xmlstarlet fo --indent-tab datei.xml

# Encoding ändern
xmlstarlet fo -e UTF-8 latin1.xml > utf8.xml

# Kompakt (eine Zeile)
xmlstarlet fo --omit-decl --indent-spaces 0 datei.xml

# Mit XML-Deklaration weglassen (für Snippet-Generation)
xmlstarlet fo --omit-decl datei.xml

fo ist auch der schnellste Weg, eine kaputt formatierte XML-Datei lesbar zu machen — gerade nach OAI-PMH-Output oder Tools, die alles in eine Zeile schreiben.

c14n — Kanonisierung

# XML-Canonical-Form (für digitale Signaturen, Diff-Vergleiche)
xmlstarlet c14n datei.xml

# Mit Kommentaren
xmlstarlet c14n --with-comments datei.xml

Relevant, wenn man zwei XML-Dateien semantisch vergleichen will, ohne durch Whitespace-Unterschiede oder Attribut-Reihenfolge gestört zu werden.

Pipeline-Patterns

xmlstarlet glänzt in der Kombination mit anderen Unix-Tools:

# Alle persName aus allen TEI-Dateien, deduplizieren, zählen
xmlstarlet sel -N t='http://www.tei-c.org/ns/1.0' \
  -t -v '//t:persName' -n *.xml \
  | sort | uniq -c | sort -rn | head -20

# Validieren und nur invalide Dateien melden
for f in *.xml; do
  xmlstarlet val --relaxng tei.rng "$f" 2>&1 \
    | grep -v ' - valid$'
done

# Massen-Update der GND-IDs aus einer Konkordanz-Tabelle
while IFS=$'\t' read -r oldid newid; do
  xmlstarlet ed --inplace \
    -u "//persName[@ref='gnd:$oldid']/@ref" \
    -v "gnd:$newid" \
    *.xml
done < konkordanz.tsv

# Aus TEI eine CSV mit Personenliste generieren
xmlstarlet sel -N t='http://www.tei-c.org/ns/1.0' \
  -t -m '//t:listPerson/t:person' \
  -v '@xml:id' -o ',' \
  -v 't:persName' -o ',' \
  -v 't:birth/@when' -o ',' \
  -v 't:death/@when' \
  -n datei.tei.xml > personen.csv

# OAI-PMH-Records pro Set ausgeben
xmlstarlet sel -N o='http://www.openarchives.org/OAI/2.0/' \
  -t -m '//o:record' -c '.' -n harvest.xml \
  | xmlstarlet fo --omit-decl

Namespaces — der Klassiker

Jede TEI-, EAD-, METS-, MODS-Datei kommt mit Default-Namespace. Wer //persName schreibt, matcht nichts. Lösungen:

# 1. Präfix binden (sauber)
xmlstarlet sel -N t='http://www.tei-c.org/ns/1.0' \
  -t -v '//t:persName' datei.xml

# 2. local-name() (pragmatisch, aber ohne Namespace-Prüfung)
xmlstarlet sel -t -v '//*[local-name()="persName"]' datei.xml

# 3. Wildcard-Namespace (xmlstarlet-spezifisch, *:name)
xmlstarlet sel -t -v '//*:persName' datei.xml

Die saubere Variante ist immer die mit gebundenem Präfix.

Häufige Fallen

  • Namespace vergessen — der häufigste Fehler. Test: xmlstarlet sel -t -v '//*' datei.xml | head zeigt, ob überhaupt Knoten gematcht werden.
  • -u ohne existierendes Attribut-u updatet nur, schafft kein neues Attribut. Erst mit -i ... -t attr einfügen.
  • In-Place-Edit ohne Backup--inplace schreibt direkt in die Datei. Vor Massen-Operationen Sicherheitskopie oder Git-Status prüfen.
  • macOS vs. GNU xmlstarlet — kleinere Unterschiede beim -i-Flag-Verhalten und beim Encoding-Default. Im Zweifel den xmlstarlet-Manpage des eigenen Systems lesen.
  • Single-Quote-Hölle — Komplexe XPath-Ausdrücke mit Apostrophen in der Shell quoting-empfindlich. $'...'-Strings (Bash) oder Heredoc-Stylesheets sind entlastend.
  • Output-Encoding — bei mehrzeiligen Strings im XML defaultmässig UTF-8. Bei alten Latin-1-Dateien explizit -e UTF-8 beim fo.
  • Riesige Dateien — xmlstarlet liest die ganze Datei in den Speicher (libxml2 DOM). Für mehrere GB grosse XML lieber SAX-Parser in Python (xml.sax) oder xmllint —stream.
  • XSLT 2.0+ Funktionenxmlstarlet tr ist 1.0. Nutzungs-Versuche mit xs:date() o. ä. scheitern stillschweigend. Für 2.0+ Saxon nutzen.

Verhältnis zu anderen Werkzeugen

  • XPath — die Sprache, die xmlstarlet ausführt; das eine ergänzt das andere.
  • xmllint (libxml2) — kann auch validieren und XPath; weniger Komfort beim Editieren.
  • xq (yq) — JSON/YAML/XML mit jq-ähnlicher Syntax; gut für gemischte JSON/XML-Pipelines.
  • xmlsh — älteres XML-Shell-Projekt, weniger verbreitet.
  • Saxon (saxon-he, saxon-ee) — für XPath/XSLT 2.0/3.x; in CI/CD oft parallel zu xmlstarlet.
  • pup / htmlq — analoges Tool für HTML mit CSS-Selektoren.
  • Pandoc — für Format-Transformation Text-orientiert; ergänzt xmlstarlet, ersetzt es nicht.
  • OpenRefine — für tabellarische Daten; xmlstarlet ergänzt für XML.
  • Regex — komplementär; Regex für Text, xmlstarlet für strukturierte XML-Bäume. Häufig in derselben Pipeline.