在bash脚本中直接使用变量存储JSON数据?

41

我想先说,“不,找另一种方法”是可以接受的答案。

有一个可靠的方法将短JSON存储在bash变量中,以便在从同一脚本运行的AWS CLI命令中使用吗?

我将从Jenkins运行作业,更新AWS Route53记录,这需要通过UPSERT JSON文件来更改记录。由于它是从Jenkins运行的,没有我可以保存此文件的本地存储,我真的很想避免每次该项目运行时都需要进行git checkout(这将是每小时一次)。

理想情况下,将数据存储在变量 ($foo) 中,并作为change-resource-record-sets命令的一部分调用,考虑到Jenkins的设置,这将是最方便的,但我不熟悉如何精确引用/存储bash内部的JSON - 是否可以安全地完成?

在这种情况下,具体JSON如下:

{"Comment":"Update DNSName.","Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"alex.","Type":"A","AliasTarget":{"HostedZoneId":"######","DNSName":"$bar","EvaluateTargetHealth":false}}}]}

作为额外的复杂性,DNSName值 - $bar - 需要扩展。

2个回答

80

您可以使用 Here-Doc:

foo=$(cat <<EOF
{"Comment":"Update DNSName.","Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"alex.","Type":"A","AliasTarget":{"HostedZoneId":"######","DNSName":"$bar","EvaluateTargetHealth":false}}}]}
EOF
)

在第一行未加引号的情况下留下EOF,here-doc中的内容将会被参数扩展,因此你的$bar会扩展为你放进去的任何内容。

如果您在JSON中可以有换行符,则可以使其更易读:

foo=$(cat <<EOF
{
  "Comment": "Update DNSName.",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "alex.",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "######",
          "DNSName": "$bar",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}
EOF
)

甚至可以(每行的第一个缩进必须是制表符)

foo=$(cat <<-EOF
    {
      "Comment": "Update DNSName.",
      "Changes": [
        {
          "Action": "UPSERT",
          "ResourceRecordSet": {
            "Name": "alex.",
            "Type": "A",
            "AliasTarget": {
              "HostedZoneId": "######",
              "DNSName": "$bar",
              "EvaluateTargetHealth": false
            }
          }
        }
      ]
    }
    EOF
)

并展示它的存储方式,包括引用(假设bar = baz):

$ declare -p foo
declare -- foo="{
  \"Comment\": \"Update DNSName.\",
  \"Changes\": [
    {
      \"Action\": \"UPSERT\",
      \"ResourceRecordSet\": {
        \"Name\": \"alex.\",
        \"Type\": \"A\",
        \"AliasTarget\": {
          \"HostedZoneId\": \"######\",
          \"DNSName\": \"baz\",
          \"EvaluateTargetHealth\": false
        }
      }
    }
  ]
}"

由于这会扩展一些 shell 元字符,所以如果您的 JSON 包含像 ` 这样的内容,可能会遇到麻烦。因此,作为替代方案,您可以直接赋值,但要注意围绕 $bar 的引用:

foo='{"Comment":"Update DNSName.","Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"alex.","Type":"A","AliasTarget":{"HostedZoneId":"######","DNSName":"'"$bar"'","EvaluateTargetHealth":false}}}]}'

注意对$bar的引用方式:它是

"'"$bar"'"
│││    │││
│││    ││└ literal double quote
│││    │└ opening syntactical single quote
│││    └ closing syntactical double quote
││└ opening syntactical double quote
│└ closing syntactical single quote
└ literal double quote

2
这个完美地解决了问题,特别感谢对于bar的引用细节的分解,这解决了一个未来的问题! - Alex
不仅是我想要的解决方案,而且它正好符合使用情况。谢谢。 - Jeff Diederiks
将语法上的单引号闭合和开启的单引号颠倒过来? - Timo
1
@Timo 不,它们被正确标记:内部的 "$bar" 在单引号字符串之外,因此第一个 ' 关闭了一个单引号字符串,第二个 ' 打开了一个新的字符串。 - Benjamin W.

18

它可以被安全地存储;生成它是另一回事,因为$bar的内容可能需要编码。让像jq这样的工具来处理创建JSON。

var=$(jq -n --arg b "$bar" '{
  Comment: "Update DNSName.",
  Changes: [
    {
      Action: "UPSERT",
      ResourceRecordSet: {
        Name: "alex.",
        Type: "A",
        AliasTarget: {
          HostedZoneId: "######",
          DNSName: $b,
          EvaluateTargetHealth: false
        }
      }
    }
  ]
}')

jq绝对是一个选择,因为我们在其他地方使用它,但我想知道缺乏引号的情况 - jq是否处理将引号放在所有内容周围还是您在示例中省略了这一点? AWS似乎要求用引号括起来的密钥对。 - Alex
2
@Alex jq 允许在过滤器中使用未加引号的简单字母数字键;生成的 JSON 将带有引号。 - chepner
1
这是 最佳 解决方案,因为 jq 可以检查 JSON 以便捕获潜在错误,并确保生成的 JSON 始终正确。 (也就是说:使用正确的工具解决问题) :) - clt60
1
来自 man jqn: 它完全不读取任何输入!相反,过滤器使用 null 作为输入运行一次。当将 jq 用作简单计算器或从头构建 JSON 数据时,这非常有用。--- 对我来说听起来像是不追加更多数据,只是一次性创建 JSON。 - Timo
我有 var=$(jq -n '{name:"a", mail:"b@", message:"hi f"}')curl --data $var localhost,但是得到了 curl: (6) Could not resolve host: "name"[...] 的错误。有什么帮助吗? - Timo
1
你没有引用 $var,所以它会被分割成单词。只有 $var 中第一个空格之前的部分被用作 -d 的参数;下一部分被视为主机名,而不是 localhost - chepner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接