使用 jq 将 JSON 数组转换为 bash 变量

EHe*_*man 24 bash jq

我有一个像这样的 JSON 数组:

{
  "SITE_DATA": {
    "URL": "example.com",
    "AUTHOR": "John Doe",
    "CREATED": "10/22/2017"
  }
}
Run Code Online (Sandbox Code Playgroud)

我希望使用 jq 迭代这个数组,这样我就可以将每个项目的键设置为变量名,将值设置为它的值。

例子:

  • 网址="example.com"
  • 作者=“约翰·多伊”
  • 创造="10/22/2017"

到目前为止,我对数组进行了迭代,但创建了一个字符串:

constants=$(cat ${1} | jq '.SITE_DATA' | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")
Run Code Online (Sandbox Code Playgroud)

哪些输出:

URL=example.com
AUTHOR=John Doe
CREATED=10/22/2017
Run Code Online (Sandbox Code Playgroud)

我希望在脚本中进一步使用这些变量:

echo ${URL}
Run Code Online (Sandbox Code Playgroud)

但这目前与空输出相呼应。我猜我需要一个eval或什么在那里,但似乎无法将我的手指放在上面。

Mic*_*mer 36

您的原始版本将eval无法运行,因为作者姓名中包含空格 - 它会被解释为运行Doe环境变量AUTHOR设置为John. 也几乎从不需要管道jq到自身 -内部管道和数据流可以将不同的过滤器连接在一起。

所有这些只有在您完全信任输入数据(例如,它是由您控制的工具生成)时才有意义。下面详细介绍了几个可能的问题,但让我们假设数据本身肯定是您目前期望的格式。

您可以制作一个更简单的 jq 程序版本:

jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)'
Run Code Online (Sandbox Code Playgroud)

输出:

URL='example.com'
AUTHOR='John Doe'
CREATED='10/22/2017'
Run Code Online (Sandbox Code Playgroud)

不需要map:.[]处理通过管道的其余部分将数组中的每个对象作为单独的 item 处理,因此最后一个之后的所有内容都|分别应用于每个对象。最后,我们只是用普通的+连接组合一个有效的 shell 赋值字符串,包括适当的引号和用@sh.

这里所有的管道都很重要——没有它们,你会得到相当无用的错误消息,其中程序的各个部分在微妙的不同上下文中进行评估。

如果您完全信任输入数据并具有您想要的效果,此字符串是eval可以的:

eval "$(jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)' < data.json)"
echo "$AUTHOR"
Run Code Online (Sandbox Code Playgroud)

与以往一样,在使用 时eval,请小心信任您获得的数据,因为如果它是恶意的或只是采用意外格式,事情可能会出错。特别是,如果密钥包含 shell 元字符(如$或 空格),这可能会创建一个正在运行的命令。例如,它还可能会PATH意外覆盖环境变量。

如果您不信任数据,要么根本不执行此操作,要么过滤对象以仅包含您首先需要的键:

jq '.SITE_DATA | { AUTHOR, URL, CREATED } | ...'
Run Code Online (Sandbox Code Playgroud)

如果是一个数组,您也可能会遇到问题,所以.value | tostring | @sh会更好 - 但这个警告列表可能是一个很好的理由,首先要这样做。


也可以建立一个关联数组,而不是同时引用键和值:

eval "declare -A data=($(jq -r '.SITE_DATA | to_entries | .[] | @sh "[\(.key)]=\(.value)"' < test.json))"
Run Code Online (Sandbox Code Playgroud)

在此之后,${data[CREATED]}包含创建日期等,无论键或值的内容是什么。这是最安全的选项,但不会导致可以导出的顶级变量。当一个值是一个数组时,它仍然可能会产生 Bash 语法错误,如果它是一个对象,它可能仍然会产生一个 jq 错误,但不会执行代码或覆盖任何东西。


cas*_*cas 17

基于@Michael Homer 的回答,您可以eval通过将数据读入关联数组来完全避免潜在的不安全。

例如,如果您的 JSON 数据位于名为 的文件中file.json

#!/bin/bash

typeset -A myarray

while IFS== read -r key value; do
    myarray["$key"]="$value"
done < <(jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + .value ' file.json)

# show the array definition
typeset -p myarray

# make use of the array variables
echo "URL = '${myarray[URL]}'"
echo "CREATED = '${myarray[CREATED]}'"
echo "AUTHOR = '${myarray[URL]}'"
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./read-into-array.sh 
declare -A myarray=([CREATED]="10/22/2017" [AUTHOR]="John Doe" [URL]="example.com" )
URL = 'example.com'
CREATED = '10/22/2017'
AUTHOR = 'example.com'
Run Code Online (Sandbox Code Playgroud)