XPath from the shell: xmllint and xmlstarlet
Two command-line tools turn XPath into something you can pipe, diff and run in a build script, including on namespaced and HTML input.
xmllint
# One-shot evaluation, prints the matched nodes serialised
xmllint --xpath '//item/title/text()' feed.xml
# Read from a pipe
curl -s https://example.com/feed.xml | xmllint --xpath 'count(//item)' -
# For HTML, relax the parser
curl -s https://example.com/ | xmllint --html --xpath '//meta[@name="description"]/@content' - 2>/dev/null
# Drop insignificant whitespace (Libxml2 2.9.9+)
xmllint --noblanks --xpath '//version/text()' pom.xml # shortcut: --xpath 'string(//version)'
# Interactive mode, where you can bind namespaces and iterate
xmllint --shell doc.xml
# > setns c=urn:example:catalog
# > xpath //c:book/@id
# > xpath count(//c:book)
# > quit- Use
string(...)ornumber(...)around the expression to get a bare value with no XML escaping. --xpathprints all matches concatenated, so pipe throughtrorsort -uwhen you want a clean list.- xmllint validates as well:
--noout --schema x.xsd doc.xmlbefore you query saves debugging time. - The exit code is non-zero when the expression matches nothing in some builds, so do not rely on it as a test result.
xmlstarlet
# select: print values, one per line (the default template for -t is -v)
xmlstarlet sel -t -m '//item' -v 'title' -n feed.xml
# select with namespace binding
xmlstarlet sel -N c=urn:example:catalog -t -v 'count(//c:book)' catalog.xml
# CSV out, useful in a report script
xmlstarlet sel -t -m '//item' \
-v 'concat(title, ",", pubDate)' -n feed.xml
# edit in place: rename an element and set an attribute
xmlstarlet ed -L -r '//legacyName' newName -a '//book[1]' -t attr -n 'lang' -v 'en' catalog.xml| Task | xmllint | xmlstarlet |
|---|---|---|
| Ad-hoc expression | --xpath EXPR file | sel -t -v EXPR file |
| Namespace prefix | --shell + setns | -N p=uri |
| Formatting output | --format | fo |
| Editing a document | Not supported | ed with in-place -L |
| Schema validation | --schema | val -s |
| HTML input | --html | --html |
💡
Both tools are XPath 1.0 only. When you need sequences, regular expressions or date arithmetic on the command line, that is a signal to move to a Saxon command line or a short script rather than fight 1.0.
FAQ
Why does my namespaced query return nothing from xmllint --xpath?
Because --xpath cannot bind prefixes. Use --shell with setns, add a dummy namespace declaration, or switch to xmlstarlet -N.
How do I get just one line out of a big document?
Wrap the expression in string() or number() so the result is a single value rather than a serialised node-set.
Related
XPath in Python: lxml and ElementTree XPath performance: cost, indexes and //
Last refreshed 2026-09-18.