27

I have a bash array

X=("hello world" "goodnight moon")

That I want to turn into a json array

["hello world", "goodnight moon"]

Is there a good way for me to turn this into a json array of strings without looping over the keys in a subshell?

(for x in "${X[@]}"; do; echo $x | sed 's|.*|"&"|'; done) | jq -s '.'

This clearly doesn't work

echo "${X[@]}" | jq -s -R '.'
that other guy
  • 116,971
  • 11
  • 170
  • 194
Jon
  • 1,785
  • 2
  • 19
  • 33

6 Answers6

48

You can do this:

X=("hello world" "goodnight moon")
printf '%s\n' "${X[@]}" | jq -R . | jq -s .

output

[
  "hello world",
  "goodnight moon"
]
kev
  • 155,172
  • 47
  • 273
  • 272
19

Since jq 1.6 you can do this:

jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}"

giving:

["hello world","goodnight moon"]

This has the benefit that no escaping is required at all. It handles strings containing newlines, tabs, double quotes, backslashes and other control characters. (Well, it doesn't handle NUL characters but you can't have them in a bash array in the first place.)

Weeble
  • 17,058
  • 3
  • 60
  • 75
  • 1
    If you add a double dash (--) after args, any string starting with a dash is not recognized as an option by jq: `X=("-sdf" "ksdfj")` `jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}"` – Dave Kok Mar 31 '22 at 10:20
  • with the space between dashes and array expansion, `-- "${X[@]}"`, the result contained an empty result, ie. `["", "hello world","goodnight moon"]`. removing the space between it, `--"${X[@]}"` gave the result expected. – Uncle Code Monkey Feb 01 '23 at 19:38
  • @UncleCodeMonkey, that sounds like you really do have an extra empty string in your array. The `--` is a single argument that tells jq "don't process anything else after this point as an option, even if it starts with a hyphen". If you remove the space, it mashes the `--` together with the first item in your array. The only way that can work is if the first item in the array is an empty string or if it's the name of a long jq option. – Weeble Feb 02 '23 at 10:30
  • @Weeble Thanks for the explanation! I think I'll leave my script as-is, but knowing _why_ it's working is preferred! =) – Uncle Code Monkey Mar 22 '23 at 13:17
  • This is just saved me 30 minutes of Chat-GPT. Thanks! – Lescai Ionel Aug 24 '23 at 14:46
9

This ...

X=("hello world" "goodnight moon" 'say "boo"' 'foo\bar')

json_array() {
  echo -n '['
  while [ $# -gt 0 ]; do
    x=${1//\\/\\\\}
    echo -n \"${x//\"/\\\"}\"
    [ $# -gt 1 ] && echo -n ', '
    shift
  done
  echo ']'
}

json_array "${X[@]}"

... yields:

["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"]

If you are planning to do a lot of this (as your reluctance to use a subshell suggests) then something such as this that does not rely on any subprocess is likely to your advantage.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Note that the property of not relying on subprocesses depends on `echo` being implemented as a shell built-in, which for `bash` it is. – John Bollinger Nov 07 '14 at 20:12
  • You should use native [[ and ]] instead [ and ] If it is bash. – Dennis V Jun 04 '20 at 19:19
  • 1
    You seem to be mixed up, @DennisV.R.. `[` is the standard utility, sometimes also available as a shell built-in (see https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html). It is `[[` ... `]]` that is bash-specific. Accordingly, I choose the former over the latter in this answer for greater portability. Note also, however, that the question *does* specify `bash` in particular via title, text, and tag. – John Bollinger Jun 04 '20 at 21:03
  • I would like to pay attention that [[ in bash is better for performance as a native part, but [ as external utility is better for compatibility, as you say. – Dennis V Jun 05 '20 at 20:54
  • A possible issue here is that if the array contains control characters (e.g. newline, tab, form-feed) it doesn't escape them correctly, and JSON doesn't allow them unescaped. – Weeble May 11 '21 at 15:00
1

You can use:

X=("hello world" "goodnight moon")
sed 's/^/[/; s/,$/]/' <(printf '"%s",' "${X[@]}") | jq -s '.'
[
  [
    "hello world",
    "goodnight moon"
  ]
]
anubhava
  • 761,203
  • 64
  • 569
  • 643
1

If the values do not contain ASCII control characters, which have to be escaped in strings in valid JSON, you can also use sed:

$ X=("hello world" "goodnight moon")
$ printf %s\\n "${X[@]}"|sed 's/["\]/\\&/g;s/.*/"&"/;1s/^/[/;$s/$/]/;$!s/$/,/'
["hello world",
"goodnight moon"]

If the values contain ASCII control characters, you can do something like this:

X=($'a\ta' $'a\n\\\"')
for((i=0;i<${#X[@]};i++));do
  [ $i = 0 ]&&printf \[
  printf \"
  e=${X[i]}
  e=${e//\\/\\\\}
  e=${e//\"/\\\"}
  for((j=0;j<${#e};j++));do
    c=${e:j:1}
    if [[ $c = [[:cntrl:]] ]];then
      printf '\\u%04x' "'$c"
    else
      printf %s "$c"
    fi
  done
  printf \"
  if((i<=${#X[@]}-2));then
    printf ,
  else
    printf \]
  fi
done
nisetama
  • 7,764
  • 1
  • 34
  • 21
0

As improve of answer

https://stackoverflow.com/a/26809278/16566807

Script produce few formats which could be usefull when include. Script meets BASH spec, checked with shellcheck.

#!/bin/bash
#
#
        X=("hello world" "goodnight moon" 'say "boo"' 'foo\bar')
#
#       set parameter to define purpose: return_format
#               php5    -> for 5.x
#               -> https://stackoverflow.com/questions/7073672/how-to-load-return-array-from-a-php-file/7073686
#               php     -> for 7.x and greater
#               json    -> for $array=@file_get_contents($f); json_decode($array, true);
#               /none/  -> for JS to JSON.Parse(myJSON);
#       function call with array as parameter: return_array "${array[@]}"
        return_array() {
                rf="${return_format}"
                if [[ $rf = "php5" ]]; then
                        q=("<?php return array(" ");")
                elif [[ $rf = "php" ]];then
                        q=("<?php return [" "];")
                elif [[ $rf = "json" ]];then
                        q=("{" "}")
                else
                        q=("[" "]")
                fi
                echo -n "${q[0]}"
                while [[ $# -gt 0 ]]; do
                        x=${1//\\/\\\\}
                        echo -n "\"${x//\"/\\\"}\""
                        [[ $# -gt 1 ]] && echo -n ', '
                        shift
                done
                echo "${q[1]}"
        }

echo "PHP 5.x"
return_format="php5"
return_array "${X[@]}"
echo "PHP 7.x"
return_format="php"
return_array "${X[@]}"
echo "JSON for PHP"
return_format="json"
return_array "${X[@]}"
echo "JSON for JS"
return_format=
return_array "${X[@]}"

will produce output:

PHP 5.x
<?php return array("hello world", "goodnight moon", "say \"boo\"", "foo\\bar");
PHP 7.x
<?php return ["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"];
JSON for PHP
{"hello world", "goodnight moon", "say \"boo\"", "foo\\bar"}
JSON for JS
["hello world", "goodnight moon", "say \"boo\"", "foo\\bar"]
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 15 '21 at 12:07