FX(1)

๐Ÿ“ฆ version 20.0.2, in epel 9

๐Ÿ’… FX(1) ๐Ÿ’…

okay bestie so this is fx, from the User Commands manual โœจ โ€” like, the whole page header is FX(1) and the manual name is literally User Commands, but we're not gonna make a whole thing of it. this is the fx page. it's the command-line JSON processing tool and it is, genuinely, iconic ๐ŸŽ€

๐ŸŒธ NAME ๐ŸŒธ

fx โ€” the command-line JSON processing tool. that's it. that's the vibe. ๐Ÿฆ‹

โœจ SYNOPSIS โœจ

fx [code ...]

so fx has two modes: cli and interactive ๐Ÿซถ

to start interactive mode, you literally just pipe any JSON into it:

$ curl ... | fx

or you can pass a filename as the first parameter:

$ fx my.json

and if any argument was passed, fx will apply it and print to stdout. simple as that ๐Ÿ’…

๐Ÿ”ฅ DESCRIPTION ๐Ÿ”ฅ

start interactive mode by not passing any arguments at all:

$ curl ... | fx

or by passing a filename as the first argument:

$ fx data.json

pass a few JSON files at once, why not:

$ cat foo.json bar.json baz.json | fx .message

use the full power of JavaScript (not me being obsessed with a one-liner):

$ curl ... | fx '.filter(x => x.startsWith("a"))'

get access to all lodash (or ramda, etc) methods by using a .fxrc file:

$ curl ... |
> fx '_.groupBy("commit.committer.name")' '_.mapValues(_.size)'

update JSON using the spread operator, main character energy:

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

extract values from maps:

$ fx commits.json | fx .[].author.name

print formatted JSON to stdout:

$ curl ... | fx .

pipe a JSON logs stream into fx:

$ kubectl logs ... -f | fx .message

and bestie. try this one. just trust me:

$ fx --life

๐Ÿงš USAGE ๐Ÿงš

๐Ÿ’œ ANONYMOUS FUNCTION

use an anonymous function as the reducer, which gets the JSON and processes it:

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

๐Ÿ’œ BINDING

if you don't pass an anonymous function param => ..., your code gets automatically transformed into an anonymous function. and you can get access to the JSON through the this keyword:

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

๐Ÿ’œ DOT

it's possible to omit the this keyword entirely:

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

and if a single dot is passed, the input JSON gets formatted but is otherwise unaltered:

$ echo '{"foo": "bar"}' | fx .
{
  "foo": "bar"
}

๐Ÿ’œ MAP

one of the frequent operations is mapping some function over an array โ€” for example, extracting some values. say you have:

[
  {
    "author": {
      "name": "antonmedv"
    }
  },
  {...},
  ...
]

and we want to collect the names of each object in the array. we can do it by mapping an anonymous function:

$ cat ... | fx '.map(x => x.author.name)'

or we can do the exact same thing with jq(1)-like syntax:

$ cat ... | fx .[].author.name
[
  "antonmedv",
  ...
]

note that [] can also be applied to map object values ๐Ÿ‘‘

$ echo '{"foo": 1, "bar": 2}' | fx .[]
[1, 2]

๐Ÿ’œ CHAINING

you can pass any number of anonymous functions for reducing the JSON:

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

๐Ÿ’œ UPDATING

you can update existing JSON using the spread operator:

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

๐Ÿ’œ EDIT-IN-PLACE

fx provides a function save which will save everything in place and return the saved object. lowkey important: this function can only be used with a filename as the first argument to the fx command ๐Ÿซถ

usage:

$ fx data.json '{...this, count: this.count+1}' save .count

๐Ÿ’œ USING PACKAGES

use any npm package by installing it globally:

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

๐ŸŒˆ FORMATTING ๐ŸŒˆ

if you need output other than JSON (for example, arguments for xargs), just don't return anything from the reducer. an undefined value is printed into stderr by default ๐Ÿ‘€

echo '[]' | fx 'void 0'
undefined

echo '[1,2,3]' |
  fx 'this.forEach(x => console.log(+x))' 2>/dev/null |
  xargs echo
1 2 3

โญ STREAMING MODE โญ

fx supports line-delimited JSON and concatenated JSON streaming ๐Ÿฆ‹

$ kubectl logs ... | fx .message

note that if an object lacks a message field, undefined will be printed to stderr. this is actually useful โ€” it's how you see that you're skipping some objects. but if you want to hide them, just redirect stderr to /dev/null ๐Ÿ’…

๐ŸŽ€ FILTERING

sometimes it's necessary to omit some messages in a JSON stream, or select only specified log messages. for this purpose, fx has special helpers select/filter โ€” pass a function into it to select/filter JSON messages:

$ kubectl logs ... |
> fx 'select(x => x.status == 500)' .message

$ kubectl logs ... |
> fx 'filter(x => x.status < 499)' .message

and if filter/select are overridden in .fxrc, you're still able to access them with a prefix: std.select(cb) or std.filter(cd) ๐Ÿซถ

๐ŸŒธ FILES ๐ŸŒธ

๐Ÿ—‚๏ธ USING .fxrc ๐Ÿ—‚๏ธ

Okay bestie, first up: create .fxrc in your $HOME directory, and use it to require any packages or define global functions. Itโ€™s giving setup era โœจ.

For example, you can access all the lodash methods without the _ prefix. Put this in your .fxrc file:

Object.assign(global, require('lodash/fp'))

And now youโ€™ll be able to call all lodash methods. For example, see whoโ€™s been committing to react recently:

curl 'https://api.github.com/repos/facebook/react/commits?per_page=100' |
  fx 'groupBy("commit.author.name")' 'mapValues(size)' toPairs \
      'sortBy(1)' reverse 'take(10)' fromPairs

See the ENVIRONMENT section.

๐Ÿ”ฎ QUERY LANGUAGE ๐Ÿ”ฎ

If you want to use a query language, for example jsonata, you can use a helper function like this:

global.jsonata = expr => require('jsonata')(expr).evaluate

And use it like this:

curl ... |
  fx 'jsonata("$sum(Order.Product.(Price * Quantity))")'

Instead, you can create this alias in your .bashrc file:

alias jsonata='FX_APPLY=jsonata fx'

And now all code arguments to jsonata will be passed through the jsonata helper. And now you can use it like this:

curl ... | jsonata '$sum(Order.Product.(Price * Quantity))'

๐ŸŽฎ INTERACTIVE MODE ๐ŸŽฎ

Click on fields to expand or collapse the JSON tree, and use the mouse wheel to scroll the view ๐Ÿ–ฑ๏ธ.

Here are the commands available in interactive mode:

  • q or Esc or Ctrl+c โ€” Exit
  • up or k โ€” Move cursor up
  • left or h โ€” Collapse
  • right or l โ€” Expand
  • Shift+right or L โ€” Expand all under cursor
  • Shift+left or K โ€” Collapse all under cursor
  • e โ€” Expand all
  • E โ€” Collapse all
  • g โ€” Scroll to top
  • G โ€” Scroll to bottom
  • . โ€” Edit filter
  • / โ€” Search
  • n โ€” Find next
  • p โ€” Exit and print JSON to stdout
  • P โ€” Exit and print fully expanded JSON to stdout

These commands are available when editing the filter:

  • Enter โ€” Apply filter
  • Ctrl+u โ€” Clear filter
  • Ctrl+w โ€” Delete last part
  • up/down โ€” Select autocomplete

๐Ÿ” SEARCHING ๐Ÿ”

Press / and type a regexp pattern to search in the current JSON. Search works with the currently applied filter ๐Ÿ‘€.

Examples of a pattern and the corresponding regexp:

  • Pattern /apple โ€” RegExp /apple/ig
  • Pattern /apple/+u โ€” RegExp /apple/
  • Pattern /apple/u+w โ€” RegExp /apple/u
  • Pattern \w+ โ€” RegExp /\w+/ig

โœ‚๏ธ SELECTING TEXT โœ‚๏ธ

You might find that you literally canโ€™t just select text in fx. Thatโ€™s because all mouse events get redirected to stdin. To be able to select again, you need to tell your terminal not to do that. You can do it by holding special keys while selecting:

  • Option+Mouse โ€” iTerm2, Hyper
  • Fn+Mouse โ€” Terminal.app
  • Shift+Mouse โ€” Linux

Note: you can press p/P to print everything to stdout and select in there.

๐Ÿง  MEMORY USAGE ๐Ÿง 

You may find that sometimes, on really big JSON files, fx prints an error message like this:

FATAL ERROR: JavaScript heap out of memory

V8 limits memory usage to around 2 GB by default. You can increase the limit by putting this line in your .profile:

export NODE_OPTIONS='--max-old-space-size=8192'

๐ŸŒˆ ENVIRONMENT ๐ŸŒˆ

To be able to require global modules, make sure you have the correct NODE_PATH env variable.

๐Ÿ’– EXAMPLES ๐Ÿ’–

โœจ BASIC EXAMPLES โœจ

$ echo '{"key": "value"}' | fx 'x => x.key'
value

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

$ echo '[1,2,3]' | fx 'this.map(x => x * 2)'
[2, 4, 6]

$ echo '{"items": ["one", "two"]}' | fx 'this.items' 'this[1]'
two

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

$ echo '{"foo": 1, "bar": 2}' | fx ?
["foo", "bar"]

๐Ÿฆ‹ OTHER EXAMPLES ๐Ÿฆ‹

Convert object to array:

$ cat package.json | fx 'Object.keys(this.dependencies)'

Or by two functions:

$ cat package.json | fx .dependencies Object.keys

By the way, fx has a shortcut for Object.keys. The previous example can be rewritten as:

$ cat package.json | fx .dependencies ?

๐Ÿ‘€ SEE ALSO ๐Ÿ‘€


Thatโ€™s the whole page, bestie! ๐Ÿซถ The source/version is FX(1) and the date is February 2021 โ€” iconic, no cap ๐Ÿ’…โœจ.

๐Ÿ“ฆ where this page comes from