我有一个 JSON 数组,如下所示:
{
"SITE_DATA": {
"URL": "example.com",
"AUTHOR": "John Doe",
"CREATED": "10/22/2017"
}
}
我希望使用 jq 迭代这个数组,这样我就可以将每个项目的键设置为变量名称,将值设置为它的值。
例子:
- 网址=“example.com”
- 作者=“约翰·多伊”
- 创建=“2017 年 10 月 22 日”
到目前为止我所得到的迭代数组但创建一个字符串:
constants=$(cat ${1} | jq '.SITE_DATA' | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")
哪个输出:
URL=example.com
AUTHOR=John Doe
CREATED=10/22/2017
我希望在脚本中进一步使用这些变量:
echo ${URL}
但这与目前的空输出相呼应。我猜我需要一个eval
或一些东西在那里,但似乎无法将我的手指放在上面。
答案1
您的原始版本将无法运行,eval
因为作者姓名中包含空格 - 它将被解释为运行Doe
环境变量AUTHOR
设置为 的命令John
。实际上也不需要通过管道传输jq
到自身 -内部管道和数据流可以将不同的过滤器连接在一起。
仅当您完全信任输入数据(例如,它是由您控制的工具生成的)时,所有这一切才有意义。下面详细介绍了几个可能的问题,但我们假设数据本身肯定采用您目前期望的格式。
您可以制作一个更简单的 jq 程序版本:
jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)'
其输出:
URL='example.com'
AUTHOR='John Doe'
CREATED='10/22/2017'
不需要map
:.[]
处理将数组中的每个对象作为单独的项目通过管道的其余部分,因此最后一个之后的所有内容|
都会分别应用于每个。最后,我们只需用普通+
连接组装一个有效的 shell 赋值字符串,包括适当的引号并在值周围转义@sh
。
所有管道在这里都很重要 - 没有它们,您会得到相当无用的错误消息,其中程序的各个部分在微妙不同的上下文中进行评估。
这个字符串eval
可以如果您完全信任输入数据并具有您想要的效果:
eval "$(jq -r '.SITE_DATA | to_entries | .[] | .key + "=" + (.value | @sh)' < data.json)"
echo "$AUTHOR"
与使用时一样eval
,请小心您信任所获取的数据,因为如果数据是恶意的或只是采用意外的格式,则可能会出现严重错误。特别是,如果钥匙包含 shell 元字符(如$
或 空格),这可以创建一个正在运行的命令。例如,它还可能PATH
意外覆盖环境变量。
如果您不信任数据,则要么根本不执行此操作,要么先过滤对象以仅包含您想要的键:
jq '.SITE_DATA | { AUTHOR, URL, CREATED } | ...'
如果以下情况,您也可能会遇到问题价值是一个数组,所以.value | tostring | @sh
会更好 - 但这个警告列表可能是一个很好的理由不是首先要做这一切。
也可以建立一个关联数组相反,键和值都被引用:
eval "declare -A data=($(jq -r '.SITE_DATA | to_entries | .[] | @sh "[\(.key)]=\(.value)"' < test.json))"
之后,${data[CREATED]}
包含创建日期等,无论键或值的内容是什么。这是最安全的选项,但不会导致可以导出的顶级变量。当值是数组时,它仍然可能会产生 Bash 语法错误;如果是对象,它仍然可能会产生 jq 错误,但不会执行代码或覆盖任何内容。
答案2
基于@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]}'"
输出:
$ ./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'
答案3
刚刚意识到我可以循环结果并评估每次迭代:
constants=$(cat ${1} | jq '.SITE_DATA' | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]")
for key in ${constants}; do
eval ${key}
done
允许我做:
echo ${AUTHOR}
# outputs John Doe
答案4
我真的很喜欢@Michel的建议。有时,您实际上可能只是提取一些变量值来使用 Bash 在该特定服务器中执行任务。因此,如果所需的变量已知,使用这种方法可以避免多次调用来jq
设置每个变量的值,甚至可以使用read
具有多个变量的语句,其中某些变量可以是有效的,也可以是空的,从而导致值偏移(即我的问题)。
我之前的方法会导致值转换错误 - 如果 .svID[ ].ID="",sv
将得到槽位号价值。
-rd '\n' getInfo sv slotID <<< $(jq -r '(.infoCMD // "no info"), (.svID[].ID // "none"), (._id // "eeeeee")' <<< $data)
如果您使用curl 下载对象,下面是我将一些变量重命名为友好名称的方法,以从数据数组中提取数据。
使用 eval 和过滤器只需一行即可解决问题,并生成具有所需名称的变量。
eval "$(jq -r '.[0] | {varNameExactasJson, renamedVar1: .var1toBeRenamed, varfromArray: .array[0].var, varValueFilter: (.array[0].textVar|ascii_downcase)} | to_entries | .[] | .key + "=\"" + (.value | tostring) + "\""' <<< /path/to/file/with/object )"
这种情况下的优点是,它将在第一步中过滤、重命名、格式化所有所需的变量。观察其中有 .[0] |如果源来自使用 GET 的 RESTful API 服务器,则响应数据如下:
[{"varNameExactasJson":"this value", "var1toBeRenamed: 1500, ....}]
如果您的数据不是来自数组,即是一个类似以下的对象:
{"varNameExactasJson":"this value", "var1toBeRenamed: 1500, ....}
只需删除初始索引:
eval "$(jq -r '{varNameExactasJson, renamedVar1: .var1toBeRenamed, varfromArray: .array[0].var, varValueFilter: (.array[0].textVar|ascii_downcase)} | to_entries | .[] | .key + "=\"" + (.value | tostring) + "\""' <<< /path/to/file/with/object )"