View on GitHub

blog

О программировании и не только

jq, yq - запросы к структурированным текстовым файлам .json, .yaml, .xml, .cvs, .props, .lua, etc

Проект jq появился в июле 2012г., автор Stephen Dolan, написан на СИ. На гитхабе у него 31.5к звёзд, этот язык запросов стал эталоном для .json файлов, доступен в виде пакета практически во всех дистрибутивах. Подробная документация, туториалы, масса примеров можно найти на сайте jqlang.org. Хороший способ освоить jq - воспользоваться сборниками рецептов JQ

Важным этапом в развитии jq стало объединение новой команды разработчиков в гитхаб организацию. По поводу этого события в NEWS.md такая запись:

After a five year hiatus we’re back with a GitHub organization, with new admins and new maintainers who have brought a great deal of energy to make a long-awaited and long-needed new release. We’re very grateful for all the new owners, admins, and maintainers. Special thanks go to Owen Ou (@owenthereal) for pushing to set up a new GitHub organization for jq, Stephen Dolan (@stedolan) for transferring the jq repository to the new organization, @itchyny for doing a great deal of work to get the release done, Mattias Wadman (@wader) and Emanuele Torre (@emanuele6) for many PRs and code reviews. Many others also contributed PRs, issues, and code reviews as well, and you can find their contributions in the Git log and on the closed issues and PRs page.

Были попытки расширить возможности jq на другие форматы файлов - YAML/XML/TOML. Это проект Андрея Кислюка, написанный на питоне wrapper, преобразующий эти форматы в .json и вызывающий jq для дальнейшей обработки.

Более радикальный подход использовал Mike Farah в проекте с таким же именем yq. Этот язык запросов написан на GO, активно развивается, более 13.3к звёзд на гитхабе. По сравнению с оригинальный jq добавлены новые возможности, но некоторые команды изменились.

Одинаковое имя пакета yq может ввести в заблуждение. Например, в Ubuntu и Debian это имя отдано проекту Андрея Кислюка:

apt-cache search ^yq
yq - Command-line YAML processor - jq wrapper for YAML documents

Поэтому лучше ставить yq через mise, или из исходников на GO, благо, это бинарник без каких-либо зависимостей - мы попробуем оба способа.

... $ mise ls-remote yq|tail
4.40.7
4.41.1
4.42.1
4.43.1
4.44.1
4.44.2
4.44.3
4.44.5
4.44.6
4.45.1
... $ mise -v use -g [email protected]
mise [email protected] ✓ installed                                                                                                                    mise ~/.config/mise/config.toml tools: [email protected]
... $ $ mise ls yq
Tool  Version  Source                      Requested 
yq    4.45.1   ~/.config/mise/config.toml  4.45

Далее можно использовать встроенный help, или читать онлайн документацию.

Теперь сделаем клон исходников и внимательно на него посмотрим - посмотрим локальные ветки (branch), ветки в original репо, создадим локальную ветку:

git branch -v
git branch -r
git checkout -b local

В проекте используется продвинутый Makefile с отдельным Makefile.variables, в них встроен минимальный хелп:

make help
Management commands for cicdtest:

Usage:
  ## Develop / Test Commands
    make build           Build yq binary.
    make install         Install yq.
    make xcompile        Build cross-compiled binaries of yq.
    make vendor          Install dependencies to vendor directory.
    make format          Run code formatter.
    make check           Run static code analysis (lint).
    make secure          Run gosec.
    make test            Run tests on project.
    make cover           Run tests and capture code coverage metrics on project.
    make clean           Clean the directory tree of produced artifacts.

  ## Utility Commands
    make setup           Configures Minishfit/Docker directory mounts.

Однако, если мы посмотрим, что выполняется в цели(target) build, увидим, что билд настроен на компиляцию исходников в контейнере podman:

make -n build
mkdir -p tmp
/bin/podman rmi -f yq_dev > /dev/null 2>&1 || true
/bin/podman build -t yq_dev -f Dockerfile.dev .
/bin/podman inspect -f "" yq_dev > tmp/dev_image_id
mkdir -p vendor
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev go mod vendor
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/format.sh
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/spelling.sh
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/secure.sh
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/check.sh
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/test.sh
mkdir -p bin/
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev go build --ldflags "-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s"
/bin/podman run --rm -e LDFLAGS="-X main.GitCommit=de2f77b4 -X main.GitDescribe=v4.45.1-19-gde2f77b4 -w -s" -e GITHUB_TOKEN="" -v /usr/local/src/gocode/yq/vendor:/go/src:z -v /usr/local/src/gocode/yq:/yq/src/github.com/mikefarah/yq:z -w /yq/src/github.com/mikefarah/yq yq_dev bash ./scripts/acceptance.sh

Мы уже настроили через mise массу инструментов, в том числе и компилятор go, можем его использовать. Для знакомства с golang выполним build с ключом -v: go build -v. При первом запуске сборки увидим самую суть гоу-программ - компилятор выкачивает из интернета код используемых модулей(библиотек), сохраняет их в кэше, и собирает из них бинарник (исполняемый файл) со всеми встроенными библиотеками:

go build -v
github.com/goccy/go-json/internal/encoder/vm_color
golang.org/x/text/transform
github.com/yuin/gopher-lua
net
golang.org/x/text/encoding
golang.org/x/text/encoding/internal
golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/japanese
golang.org/x/text/encoding/korean
golang.org/x/text/encoding/simplifiedchinese
golang.org/x/text/encoding/traditionalchinese
golang.org/x/text/runes
golang.org/x/text/internal/tag
golang.org/x/text/internal/language
golang.org/x/text/encoding/unicode
golang.org/x/text/unicode/norm
golang.org/x/text/internal/language/compact
golang.org/x/text/language
gopkg.in/yaml.v3
golang.org/x/text/encoding/htmlindex
golang.org/x/net/html/charset
text/template/parse
crypto/x509
net/textproto
vendor/golang.org/x/net/http/httpguts
vendor/golang.org/x/net/http/httpproxy
mime/multipart
log/syslog
gopkg.in/op/go-logging.v1
github.com/spf13/pflag
crypto/tls
text/template
github.com/spf13/cobra
github.com/goccy/go-json/internal/encoder/vm_indent
net/http/httptrace
net/http
github.com/magiconair/properties
github.com/goccy/go-json/internal/encoder/vm
github.com/goccy/go-json
github.com/mikefarah/yq/v4/pkg/yqlib
github.com/mikefarah/yq/v4/cmd

Последующие сборки будут происходить практически мгновенно при использовании локально установленного golang и кэша. При сборке в контейнере также можно настроить локальное кэширование, и у docker, и у podman в документации есть подсказки как это сделать docker: use-cache-mounts, podman option –mount=type=cache.

Если посмотрим на размер и свойства свежесобранного yq, то увидим, что бинарник собран with debug_info, not stripped, почистим это такими командами:

file yq
yq: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9844dfd7200a5cbdb9e59bd299051c8589c15f8e, with debug_info, not stripped
ll -h yq
-rwxr-xr-x 1 ophil ophil 16M Apr 16 11:54 yq*
strip -v yq
copy from `yq' [elf64-x86-64] to `stKq9GDT' [elf64-x86-64]
file yq
yq: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9844dfd7200a5cbdb9e59bd299051c8589c15f8e, stripped
ll -h yq
-rwxr-xr-x 1 ophil ophil 11M Apr 16 12:28 yq*

Утилита yq готова, но пока без man-страницы, только встроенный help. Обратим внимание - в репозитарии много .md файлов, похоже, в них хранится всё содержимое сайта. А также в каталоге scripts есть пара интересных файлов: generate-man-page-md.sh и generate-man-page.sh. Если запустить их в таком же порядке, получим 2 новых файла:

file man.md yq.1
man.md: Unicode text, UTF-8 text, with very long lines (389)
yq.1:   troff or preprocessor input, Unicode text, UTF-8 text

Теперь достаточно упаковать второй из файлов и разместить его в соответствующем каталоге для страниц man:

gzip yq.1
sudo mkdir -pv /usr/local/share/man/man1
sudo cp yq.1.gz /usr/local/share/man/man1

Мы собрали yq из исходников, добавили ман-страницу, можно приступать к изучению языка запросов.

Например, главное преимущество yq - форматы входных и выходных файлов:

... $ yq -h|grep put-format
# Use the '-p/--input-format' flag to specify a format type.
  -p, --input-format string           [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|lua|l] parse format for input. (default "auto")
  -o, --output-format string          [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|shell|s|lua|l] output format type. (default "auto")

Ещё полезный ключик для редактирования файла или очистки от мусора - пустых строк, концевых пробелов:

... $ yq -h|grep inplace
  -i, --inplace                       update the file in place of first file given.

вернуться обратно в блог