26

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)'

3 Answers 3

31

jq can reference expressions inside string using \(foo)

String interpolation - \(foo)

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"

Any jq syntax is valid between the parentheses: for example, array iteration, field access. So

echo '["a", "b", "c"]' | jq -r '"Testing \(.[])"'

yields 3 lines of output:

Testing a
Testing b
Testing c
0
19

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 %10s\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)"'

(not to be used with arbitrary JSON where title/number are not guaranteed to be scalar. If they could be arrays, that would constitute an arbitrary command execution vulnerability).

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).

1
  • 4
    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. Commented Aug 27, 2021 at 18:02
18

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.)

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .