16

jq has built-in ability to convert numbers to string or concatenate strings.
How can I format strings inside jq similar to printf like padding (%4s).

For example, how can I force number to occupy 10 char spaces with left alignment?
echo '{"title" : "A sample name", "number" : 1214}' | jq '(.title) + " " + (.number | tostring)'

Zeta.Investigator
  • 880
  • 1
  • 7
  • 25

3 Answers3

16

One way of doing it would be to not trying to do it in jq, and instead use jq to output a shell statement to do it in the shell instead:

eval "$(
    jq -r -n '
        { "title": "A sample name", "number": 1214 } |
        [ "printf", "%s %10s\\n", .title, .number ] | @sh'
)"

or,

eval "$(
    printf '%s\n' '{ "title": "A sample name", "number": 1214 }' |
    jq -r '[ "printf", "%s %10d\\n", .title, .number ] | @sh'
)"

or,

printf '%s\n' '{ "title": "A sample name", "number": 1214 }' |
{
    eval "$(
        jq -r '[ "printf", "%s %10s\\n", .title, .number ] | @sh'
    )"
}

The jq command would output

'printf' '%s %10d\n' 'A sample name' 1214

using the @sh operator to properly quote each bit of the command safely. When evaluated, this would output

A sample name       1214

A similar approach, but giving you two variable assignments instead:

jq -r -n '
    { "title": "A sample name", "number": 1214 } |
    @sh "title=\(.title)",
    @sh "number=\(.number)"'

You would then use these variables in your script:

unset -v title number

eval "$(
    jq -r -n '
        { "title": "A sample name", "number": 1214 } |
        @sh "title=\(.title)",
        @sh "number=\(.number)"'
)"

printf '%s %10s\n' "$title" "$number"

For cases where the data is known to be nice (the title can't contain newlines, for example), you could possibly do

jq -r -n '
    { "title": "A sample name", "number": 1214 } |
    [ .title, .number ] | @sh' |
xargs printf '%s %10s\n'

That is, make sure that the data is quoted, and then pass it to printf in the shell (this would call the external utility printf, not a shell built-in).

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • 3
    Beware `jq` outputs numbers in English format (with `.` as the decimal radix character for instance), while `printf` (for those implementations that accept floats for `%d`) expects it in the locale format. Several `printf` implementations like that of zsh or ksh88/ksh93 (those found as `sh` on some systems) will interpret arguments for `%d` as arithmetic expressions and as such can execute arbitrary commands, so it would be important to sanitise the input if those `.number` can't be guaranteed to be numbers. Using `%10s` instead of `%10d` would avoid the problem. – Stéphane Chazelas Aug 27 '21 at 18:02
12

jq does not have printf. One way could be; Partially taken from here:

echo '{"title" : "A sample name", "number" : 1214}' | 
jq '(.title) + " " + 
    (.number | tostring | (" " * (10 - length)) + .)'

Perhaps better added as a module.


Personally I quickly find jq lines to be somewhat messy and resort to perl, python, php or similar if doing anything beyond the basics. (But that is me :P)

E.g with php:

#! /usr/bin/env php
<?php

$data = json_decode(file_get_contents("php://stdin"));

printf("%s: %10d\n", $data->title, $data->number);

?>

(Would add error-checking etc as well of course.)

ibuprofen
  • 2,781
  • 1
  • 14
  • 33
12

jq can reference expressions inside string using \(foo)

[String interpolation - \(foo)][1]

Inside a string, you can put an expression inside parens after a backslash. Whatever the expression returns will be interpolated into the string.

jq '"The input was \(.), which is one less than \(.+1)"' <<<  42

Result:

"The input was 42, which is one less than 43"

[1]: https://stedolan.github.io/jq/manual/#Stringinterpolation-\\(foo)

AdminBee
  • 21,637
  • 21
  • 47
  • 71
dosmanak
  • 221
  • 2
  • 5