bashkit

jq builtin#

Bashkit ships an embedded jq JSON processor backed by jaq with a thin compatibility shim layered on top. This guide documents which jq features are supported so callers (and LLM agents that generate jq filters against bashkit) can avoid surprises.

Reported version#

jq --version prints jq-1.8. Filters generated for stedfan/jq 1.7 and 1.8 are the intended target.

Command-line flags#

Implemented:

FlagDescription
-r, --raw-outputStrings are written without quotes
-R, --raw-inputEach line of input becomes a JSON string
-s, --slurpRead every input value into one array
-n, --null-inputUse null as the (single) input value
-c, --compact-outputOne JSON value per line, no pretty-printing
-S, --sort-keysSort object keys recursively
-e, --exit-statusSet exit code based on the output
-jLike -r but suppresses trailing newlines
--tabUse tabs for indentation
-a, --ascii-outputEscape non-ASCII characters as \uXXXX (strings stay quoted even with -r)
--raw-output0Like -r with a NUL after each output; a string containing NUL is an error
--seqWrite RS (0x1e) before each output; RS separates input values
--streamFeed each input as path events ([path, leaf], [path]), as tostream does
--arg name valueBind $name to a string
--argjson name jsonBind $name to a parsed JSON value
-f FILE, --from-file FILERead the filter from FILE (VFS); all positionals become input files
-V, --versionPrint the version
-h, --helpPrint help
Combined flags like -snrTreated as the union of the individual flags

Variables#

VariableBehaviour
$ENVBound to the shell environment as an object, same map as the env filter. (#1486)
$nameVariables defined with --arg / --argjson are passed through.

Notable filters#

The full jq stdlib is mostly available via jaq-std. The compatibility shim adds or overrides:

FilterNotes
envReads from the shell env map (not the host process env), avoiding the unsafe std::env::set_var path.
setpath(p; v)Bashkit ships a recursive definition because jaq’s stdlib doesn’t expose one.
leaf_pathsDefined as paths(scalars) since jaq’s stdlib lacks it.
match(re; flags) / match(re)Overridden to add "name": null to unnamed captures, matching jq output.
scan(re; flags) / scan(re)Overridden so scan defaults to global (“g”) matching, matching jq.
input_filenameName of the file the current value came from (null for stdin).
input_line_numberLines read so far, as jq counts them (jq reads a line at a time).
input / inputsPull from the same stream as the main loop; with -n the whole stream is theirs. input fails with No more inputs at the end.
Most other 1.7/1.8 stdlib filtersForwarded from jaq-std (getpath, paths, to_entries, group_by, ltrimstr/rtrimstr, splits, test, now, debug, limit, etc.).

Numbers#

Computed numbers print like jq: pow(2;10) is 1024, 1e17*1 is 1e+17, 0.00001*1 is 1e-05, NaN is null and infinities print as the largest double. Input literals print as read until arithmetic changes them (1.0 stays 1.0). Division by zero is an error, % truncates its operands to integers, a fractional array index is truncated, and gamma is the log-gamma function, all as in jq. Math and index errors use jq’s wording, so try ("a"+1) catch . gives string ("a") and number (1) cannot be added.

Errors#

Filter compile failures exit 3. A runtime error is reported as jq: error (at FILE:LINE): ... (<stdin>, or <unknown> under -n) and jq moves on to the next input; the exit status follows the last input (5 if it failed). error("msg") prints msg unquoted, other values get jq’s (not a string) marker. Values before a JSON syntax error are processed, then the error exits 5. A file that cannot be opened prints Could not open file F: reason, is skipped, and makes the exit status 2. With -e the last output decides: 1 for null/false, 4 when nothing was output. Long error operands are summarised so failures do not blow up an LLM context window, see #1485.

Resource limits#

A filter cannot allocate without bound. Every live string, array and object counts against ExecutionLimits::max_live_intermediate_bytes (32 MB by default); growing past it fails before allocating with jq: error: value size limit (N bytes) exceeded and exit 5, and try cannot catch its way around it. Output is capped by max_stdout_bytes, and a filter that loops without emitting (until(false; .)) stops at the execution timeout with jq: execution timed out.

--stream produces path events incrementally, sharing ancestor keys. Event and traversal storage obey the same memory limit, and traversal checks work, cancellation and timeout limits even with an empty filter. --slurp retains events and can hit the memory limit on otherwise small JSON input.

The limit counts real in-memory size, which is several times the JSON text: a 5.6 MB array of 100,000 small objects fits the default, much larger inputs need a larger max_live_intermediate_bytes.

Location#

$__loc__ is {"file":"<top-level>","line":N}, N being the filter line it appears on.

Destructuring alternatives#

. as [$a, $b] ?// {a: $a} | ... tries each pattern in turn, as in jq: every variable named in any pattern is bound (null when its pattern did not set it), and an error moves on to the next pattern.

Messages and halt#

stderr, debug and halt_error write to the jq command’s stderr. halt and halt_error end the jq command (not the shell or host) with their exit code, skipping any remaining input.

Known gaps#

Bashkit’s jq is intentionally minimal in places where the host model differs from upstream jq:

  • --seq reads RS as whitespace; jq’s rules for abandoned text between separators are not reproduced.
  • Exotic numeric formatting modes (@base32, @base64d, etc.) follow whatever jaq-json ships.

If you hit a missing builtin, please open an issue with the failing filter.

See also#