有没有办法将 jq 输出到 bash 脚本的多个变量中?

Is there a way to output jq into multiple variables for bash script?

基本上我有一个 bash 脚本,它在某一时刻发出 API 调用并生成证书和密钥并在 json 中返回。我将它通过管道传输到 jq 并且可以 select 证书或密钥并将其存储在变量中。

像这样:

CERT=$(API call | jq -r '.certificate')
or
KEY=$(API call | jq -r '.key')

我想将每个存储在它自己的变量中,但我不能调用两次,因为它会生成一个新的 cert/key。

我知道我可以将两者都存储在一个文件中,然后在完成我的任务后进行操作,但我很好奇 jq 是否提供了一种直接的方法来 select 将每个值主动存储在它自己的变量中?

您可以将原始 JSON 存储在变量(而不是文件)中,然后从中提取密钥和证书:

apiResult=$(API call)
cert=$(jq -r '.certificate' <<<"$apiResult")
key=$(jq -r '.key' <<<"$apiResult")

注意:我建议使用小写或混合大小写的变量,以避免与许多具有特殊含义的全大写名称发生意外冲突。此外,<<< 是一种 bashism,不适用于所有其他 shell;如果您需要它可以移植到例如破折号,使用类似 key=$(printf '%s\n' "$apiResult" | jq -r '.key').

如果两个值都不包含换行符,您可以轻松使用 read:

$ read cert key < <( echo '{"certificate": "foo", "key": "bar"}' | jq -r .certificate,.key | tr \n ' ')
$ echo $cert
foo
$ echo $key
bar

或:

$ { read cert; read key; } << EOF
> $( echo '{"certificate": "foo", "key": "bar"}' | jq -r .certificate,.key )
> EOF
$ echo $cert:$key
foo:bar

证书几乎肯定会包含换行符,因此您可能希望序列化该数据(例如,对其进行 base64 编码)。但是您最好使用 Gordon Davisson 的方法。

除非字符串中有 NUL 字符,否则不需要多次调用 jq 或序列化数据。

在最简单的情况下,您可以按照以下几行进行:

{ IFS= read -r certificate
  IFS= read -r key
  echo "certificate=$certificate"
  echo "key=$key"
} < <(API call | jq -r '.certificate, .key')

如果值不包含 NUL 但可能包含换行符, 那么你可以使用 NUL 作为分隔符。为了多样化, 我们还可以使用 while 循环:

while IFS= read -r -d $'[=11=]' certificate
do
  IFS= read -r -d $'[=11=]' key
  echo "certificate=$certificate"
  echo "key=$key"
done < <(API call | jq -rj '[.certificate, .key] | join("\u0000")')

反之...

如果感兴趣的值可能包含文字 NUL 值(“\u0000”),那么这个问题似乎有问题,因为 bash 变量实际上不能包含文字 NUL。

如果任何感兴趣的值可能包含文字 NUL 值,那么这里有两种将“原始”字符串等价物提取到单独文件中的策略:

  1. 将 JSON 输出保存在一个(临时)文件中,并以显而易见的方式对每个感兴趣的值调用一次 jq -r

  2. 设置一个 bash 管道,开始于:

    API 呼叫 | jq -r '.certificate, .key | @base64`

    并继续一个循环,其中每一行都被解码,例如使用 base64 --decode 或 jq 的 @base64d.

如果 API 调用生成非常大的 JSON 文档,则第二种策略可能有意义。

假设您创建了一个输出 shell 命令的程序。

$ data_source | jq -r '@sh "certificate=\( .certificate )\nkey=\( .key )"'
certificate='the certificate'
key='the key'

然后,您需要做的就是评估它们。

eval "$( data_source | jq -r '@sh "certificate=\( .certificate )\nkey=\( .key )"' )"

如果您要处理很多变量,您可以使用以下方法:

eval "$(
   data_source |
   jq -r '
      { "certificate": "certificate", "key": "key" } as $map | 
      ( $map | keys_unsorted[] ) as $shell_var |
      $shell_var + @sh "=\( .[$map[$shell_var]] )"
   '
)"

您甚至可以使用路径。

eval "$(
   data_source |
   jq -r '
      { "certificate": [ "certificate" ], "key": [ "key" ] } as $map | 
      ( $map | keys_unsorted[] ) as $shell_var |
      $shell_var + @sh "=\( getpath($map[$shell_var]) )"
   '
)"