xmllint из libxml2 проверяет, что XML правильно сформирован, валидирует его по DTD или XSD-схеме, красиво форматирует и достаёт данные XPath-запросами. Для XML это то же, что jq для JSON.
Синтаксис
xmllint [опции] файл.xml…
Вместо имени файла можно указать -, тогда XML читается из stdin. Без опций xmllint разбирает документ и печатает его обратно — так видны ошибки разбора.
Проверка и валидация
| Опция | Что делает |
|---|
--noout | не печатать документ — только сообщить об ошибках |
--valid | проверить по DTD, объявленному в самом документе (<!DOCTYPE …>) |
--dtdvalid файл.dtd | проверить по внешнему DTD |
--schema файл.xsd | проверить по XML Schema (XSD) |
--relaxng файл.rng | проверить по схеме RELAX NG |
--recover | при ошибках разбора вывести то, что удалось разобрать |
--html | разбирать как HTML (прощает незакрытые теги) |
--nonet | не скачивать DTD и сущности из сети |
--huge | снять внутренние лимиты на размер документа |
Вывод и преобразование
| Опция | Что делает |
|---|
--format | вывести с отступами; ширину отступа задаёт переменная XMLLINT_INDENT (по умолчанию два пробела) |
--noblanks | убрать незначащие пробелы и переводы строк — XML в одну строку |
--output файл (-o) | записать результат в файл вместо stdout |
--encode КОДИРОВКА | вывести в другой кодировке |
--c14n | каноническая форма: атрибуты, кавычки и пустые теги в едином виде; вместе с --noblanks удобно сравнивать документы через diff |
--dropdtd | убрать <!DOCTYPE …> |
--noent | подставить значения сущностей |
--xinclude | выполнить <xi:include> |
--nsclean | убрать лишние объявления пространств имён |
Запросы
| Опция | Что делает |
|---|
--xpath 'выражение' | выполнить XPath и напечатать результат (документ при этом не печатается) |
--shell | интерактивная оболочка: cat //title, ls, cd, exit |
XPath: пути
| Выражение | Что выбирает |
|---|
/catalog/book | элементы book прямо внутри корня catalog |
//title | все title на любой глубине |
*, @* | любой элемент / любой атрибут |
//book/@id | атрибуты id |
//title/text() | текст внутри элементов, без тегов |
.. | родитель: //title[../@id="3"] |
//book[@lang="ru"] | отбор по атрибуту |
//book[price > 8] | отбор по значению дочернего элемента |
//book[1], //book[last()], [position() < 3] | по номеру у своего родителя (с 1), последний, первые два |
(//title)[2] | второй title во всём документе |
a | b | объединение двух наборов узлов |
parent::, ancestor::, following-sibling::, preceding-sibling:: | оси: родитель, предки, соседи после / до |
XPath: функции и операторы
| Функция | Что делает |
|---|
string(…) | текстовое значение первого найденного узла |
count(…), sum(…) | количество узлов, сумма значений |
contains(a, b), starts-with(a, b) | подстрока, префикс |
normalize-space(…) | обрезать пробелы по краям и схлопнуть внутренние |
concat(a, b, …), string-length(…), translate(s, abc, ABC) | склейка, длина, посимвольная замена |
name(…), local-name(…) | имя элемента с префиксом / без префикса пространства имён |
not(…), boolean(…) | отрицание, «нашлось ли» |
and, or, =, !=, <, > | логика и сравнения в условиях […] |
Коды выхода
0 — всё в порядке, 1 — ошибка разбора (XML сломан) или файл не найден, 3 / 4 — документ не прошёл валидацию по схеме / DTD, 10 — XPath ничего не нашёл или выражение с ошибкой.
Примеры
cat > books.xml <<'EOF'
<catalog><book id="1" lang="en"><title>Dune</title><price>9.99</price></book><book id="2" lang="ru"><title>Solaris</title><price>7.50</price></book></catalog>
EOF
# проверить синтаксис: молчит, если всё хорошо
xmllint --noout books.xml && echo OK
# красиво отформатировать
xmllint --format books.xml
# элементы целиком, с тегами
xmllint --xpath '//title' books.xml
# только текст — по строке на узел
xmllint --xpath '//title/text()' books.xml
# одно значение: заголовок книги с id=2
xmllint --xpath 'string(//book[@id="2"]/title)' books.xml
# значение атрибута без обёртки id="…"
xmllint --xpath 'string(//book[1]/@lang)' books.xml
# сколько книг и сумма цен
xmllint --xpath 'count(//book)' books.xml
xmllint --xpath 'sum(//price)' books.xml
# поиск по части текста
xmllint --xpath '//book[contains(title, "un")]/@id' books.xml
# проверить документ по XSD-схеме
xmllint --noout --schema catalog.xsd books.xml
# XML из stdin
echo '<a><b>1</b></a>' | xmllint --format -
# сравнить два документа без учёта форматирования
diff <(xmllint --noblanks --c14n a.xml) <(xmllint --noblanks --c14n b.xml)
Подводные камни
string() берёт только первый узел. string(//title) вернёт Dune, а не все названия. Для всех значений используй //title/text(), для N-го — string((//title)[N]).
//book[2] — не «вторая книга в документе», а каждая книга, которая вторая у своего родителя. Для сквозной нумерации нужны скобки: (//book)[2].
//book/@id печатает id="1" вместе с именем атрибута. Чтобы получить только значение, бери string(…/@id).
- Пустой результат — это ошибка. Если XPath ничего не нашёл,
xmllint пишет XPath set is empty и завершается с кодом 10. А string() от пустого набора — просто пустая строка и код 0.
- Пространства имён. Если у корня есть
xmlns="…", запрос //v ничего не найдёт, а //p:v даёт Undefined namespace prefix. Обходной путь: //*[local-name()="v"].
- Пробелы в тексте сохраняются.
<title> Dune </title> вернёт значение с пробелами — сравнение = не сработает. Используй normalize-space().
- Кавычки. Выражение бери в одинарные кавычки, а строки внутри XPath — в двойные:
'//book[@lang="ru"]'.
- В старых версиях libxml2 результаты
text() склеиваются без перевода строки. Если значения слиплись, перебирай узлы по индексу.
См. также
jq · yq · grep · sed · iconv