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(...) or number(...) around the expression to get a bare value with no XML escaping.
  • --xpath prints all matches concatenated, so pipe through tr or sort -u when you want a clean list.
  • xmllint validates as well: --noout --schema x.xsd doc.xml before 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
Taskxmllintxmlstarlet
Ad-hoc expression--xpath EXPR filesel -t -v EXPR file
Namespace prefix--shell + setns-N p=uri
Formatting output--formatfo
Editing a documentNot supporteded with in-place -L
Schema validation--schemaval -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.

XPath in Python: lxml and ElementTree XPath performance: cost, indexes and //

Last refreshed 2026-09-18.