Как стать автором
Обновить

fx — алтернатива jq для обработки JSON из командной строки

Время на прочтение2 мин
Количество просмотров8.9K


jq — самая популярная утилита для обработки JSON из командной строки, написана на C и имеет свой собственный синтаксис для работы с JSON.


Однако, обрабатывать JSON в командной строке не нужно очень часто, а когда потребность возникает, приходится мучиться с незнакомым языком программирования.


Так и появилась идея написать fx с простым и понятным синтаксисом, который никогда не забудешь. А какой язык программирования знают все? Правильно — JavaScript.


fx принимает в качестве аргумента код на JavaScript, содержащий анонимную функцию, и вызывает её, подставляя в качестве аргумента JSON, полученный из stdin. То, что функция вернёт, будет выведено в stdout. Все. Больше никаких сложных и непонятных конструкций.


Сравните два решения одной и той же задачи на jq и на fx:


jq '[.order[] as $x | .data[$x]]'

fx 'input => input.order.map(x => obj.data[x])'

Чуть более многословно? Да, это же просто обычный JavaScript.


Если fx вообще не передать аргументов, то JSON будет отформатирован и выведен:


$ echo '{"key":"value"}' | fx
{
    "key": "value"
}

Если код не содержит param =>, то переданный код будет автоматически преобразован в анонимную функцию и this будет содержать переданный JSON объект:


$ echo '{"foo": [{"bar": "value"}]}' | fx 'this.foo[0].bar'
"value"

fx можно передать несколько аргументов/анонимных функций, они будут применены поочерёдно к JSON:


$ echo '{"foo": [{"bar": "value"}]}' | fx 'x => x.foo' 'this[0]' 'this.bar'
"value"

Если код содержит ключевое слово yield, созданная анонимная функция будет содержать "развернутый" генератор (пример из generator-expression):


$ cat data.json | fx '\
for (let user of this) \ 
  if (user.login.startsWith("a")) \
    yield user'

$ echo '["a", "b"]' | fx 'yield* this'
[
    "a",
    "b"
]

Это позволяет описывать очень простые выборки в сложных запросах.


Кстати, модифицировать JSON с fx тоже очень просто:


$ echo '{"count": 0}' | fx '{...this, count: 1}'
{
    "count": 1
}

А также можно использовать любой npm пакет, если поставить его глобально:


$ npm install -g lodash
$ cat package.json | fx 'require("lodash").keys(this.dependencies)'

Для некоторых важно, что jq всего лишь один бинарник (~2mb). Так вот fx тоже имеет отдельные бинарники.


Весят они немного больше (~30mb), но если вам это не критично, и стоит nodejs, то поставить fx можно с помощью npm:


npm install -g fx

А что по производительности? Давайте замерим с помощью hyperfine:


$ curl 'https://api.github.com/repos/stedolan/jq/commits' > data.json
$ hyperfine --warmup 3 "jq ..." "fx ..."
Benchmark #1: cat data.json | jq '[.[] | {message: .commit.message, name: .commit.committer.name}]'

  Time (mean ± σ):      10.7 ms ±   0.9 ms    [User: 8.7 ms, System: 3.6 ms]

  Range (min … max):     9.0 ms …  14.9 ms

Benchmark #2: cat data.json | fx 'this.map(({commit}) => ({message: commit.message, name: commit.committer.name}))'

  Time (mean ± σ):     159.6 ms ±   4.4 ms    [User: 127.4 ms, System: 28.4 ms]

  Range (min … max):   153.0 ms … 170.0 ms

На порядок меньше. А все дело в том, что fx использует eval для запуска кода из аргументов (и вообще весь код fx <70 строк кода). Если для вас важна скорость обработки, не используйте fx. Во всех остальных случаях — fx отличный выбор.



Надеюсь, эта утилита кому-нибудь пригодится. Спасибо за внимание.

Теги:
Хабы:
Всего голосов 23: ↑14 и ↓9+5
Комментарии19

Публикации