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 отличный выбор.



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

Комментарии (15)


  1. Self_Perfection
    30.01.2018 09:26

    Я наивно полагал, что самая популярная утилита для работы с json из командной строки это jshon. Вдвое быстрее jq да и мне как человеку, для которого наиболее привычный язык — шелл скриптинг, синтаксис команд кажется наиболее внятным:

    $ time for i in {1..100}; do jq '.["foo"] | .[0] | .["bar"]' <<< '{"foo": [{"bar": "value"}]}' >/dev/null; done   
    
    real    0m0,288s
    user    0m0,186s
    sys     0m0,100s
    
    $ time for i in {1..100}; do jshon -e foo -e 0 -e bar <<< '{"foo": [{"bar": "value"}]}' >/dev/null; done
    
    real    0m0,158s
    user    0m0,078s
    sys     0m0,043s


    Функциональности у jshon вроде меньше, но не помню ситуаций, чтобы мне её не хватало.


  1. playnet
    30.01.2018 11:28

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


    1. TheKnight
      30.01.2018 13:15

      К счастью, не все знаю JavaScript.


  1. welcomerooot
    30.01.2018 11:58

    Пожалуйста, хватит тащить JS туда, где он не нужен.


    1. alexey-m-ukolov
      30.01.2018 13:25
      +1

      Кому-то не нужен, кому-то нужен, в чём проблема? Никого же силком не тащат, а для тех, кто с консолью на вы, но вынужден решать какую-то срочную задачу и знает js, вполне себе удобный инструмент может быть.


      1. akamensky
        31.01.2018 05:40

        Мне кажется те, кто «с консолью на вы», не должны решать срочных задач в консоли, потому что в 99% случаев (appx/bome) это заканчивается поисками тех кто с консолью работает каждый день чтобы починить не работающий сервис.


        1. alexey-m-ukolov
          31.01.2018 13:16
          +1

          Конечно, в продакшене — да. Но бывают и другие окружения совершенно не требовательные к компетенциям работающего. И если такие люди будут использовать хорошо им знакомый язык в рамках малознакомой системы, вероятность падения только уменьшится.

          Мне сам подход непонятен — «инструмента не должно существовать, потому что он написан на языке, который мне не нравится». Кому-то он может быть полезен, а те, кому нравятся другие спокойно могут их использовать. Чем больше разнообразных инструментов доступно, тем лучше.


    1. potan
      31.01.2018 00:42

      А он где-то нужен? Ну кроме как в качестве ассемблера для компиляции других языков в веб-приложения.


  1. dimcha
    30.01.2018 14:53
    +1

    fx написана на js и требует среды исполнения. На сервак nodejs тащить как-то стремно. Вот если-бы вы fx на Си написали и использовали embedded js engine, типа Duktape, цены утиле было-бы гораздо больше.


    1. Elfet Автор
      30.01.2018 15:51
      +1

      У fx есть независимый бинарники: https://github.com/antonmedv/fx/releases
      Просто качайте и используйте, никаких зависимостей.


      1. TheKnight
        30.01.2018 19:41

        Там внутри статически слинкованная нода и все зависимости, я правильно понимаю?


  1. Elfet Автор
    30.01.2018 15:51

    Ошибся веткой


  1. ALexhha
    30.01.2018 21:53

    Так и появилась идея написать fx с простым и понятным синтаксисом, который никогда не забудешь.
    я конечно извиняюсь, но для кого этот синтаксис будет простым и понятным- для js'еров? Например, для меня он не простой и не понятный

    Для некоторых важно, что jq всего лишь один бинарник (~2mb). Так вот fx тоже имеет отдельные бинарники. Весят они немного больше (~30mb)
    <зануда mode=on>Ну как бы не немного, а на порядок<зануда mode=off>

    и вообще весь код fx <70 строк кода

    это все хорошо, но при этом бинарник весит 35 Мб

    Во всех остальных случаях — fx отличный выбор.
    очень спорное утверждение

    P.S.
    как ни странно, но в плане работы с JSON мне нравится aws cli с его --query, который поддерживает JMESPath


  1. tgz
    31.01.2018 09:46

    Лучше бы к jq приделали yaml, чем изобретать велосипед со страпоном вместо седла.


  1. KIVagant
    31.01.2018 16:37

    Ну… тут конечно набросились выше, но я считаю, что автор сделал хорошее дело. Да, на продакшн я бы это не ставил. Даже в докер-контейнер затаскивать как временное решение для дебага — не захочется, особенно если ещё и с нодой сверху. Но если бы я писал много и часто на nodejs, то иметь такой же синтаксис для разбора выхлопа json локально в процессе разработки — вай нот? В общем, плюсую. Уверен, если популяризировать, то найдутся благодарные пользователи.