【问题标题】:Build a JSON string with Bash variables使用 Bash 变量构建 JSON 字符串
【发布时间】:2018-07-06 07:20:58
【问题描述】:

我需要将这些 bash 变量读入我的 JSON 字符串,但我不熟悉 bash。任何帮助表示赞赏。

#!/bin/sh

BUCKET_NAME=testbucket
OBJECT_NAME=testworkflow-2.0.1.jar
TARGET_LOCATION=/opt/test/testworkflow-2.0.1.jar

JSON_STRING='{"bucketname":"$BUCKET_NAME"","objectname":"$OBJECT_NAME","targetlocation":"$TARGET_LOCATION"}'


echo $JSON_STRING 

【问题讨论】:

标签: json bash quoting


【解决方案1】:

除了chepner's answer,还可以使用这个简单的配方完全从 args 构造对象:

BUCKET_NAME=testbucket
OBJECT_NAME=testworkflow-2.0.1.jar
TARGET_LOCATION=/opt/test/testworkflow-2.0.1.jar

JSON_STRING=$(jq -n \
                  --arg bucketname "$BUCKET_NAME" \
                  --arg objectname "$OBJECT_NAME" \
                  --arg targetlocation "$TARGET_LOCATION" \
                   '$ARGS.named')

解释:

  • --null-input | -n 禁用读取输入。来自手册页:Don't read any input at all! Instead, the filter is run once using null as the input. This is useful when using jq as a simple calculator or to construct JSON data from scratch.
  • --arg name value 将值作为预定义变量传递给程序:value 可用作 $name。所有命名参数也可用作$ARGS.named

因为$ARGS.named的格式已经是一个对象,所以jq可以原样输出。

【讨论】:

  • 这必须是一个较新的功能(使用起来会很好),但 jq 版本 jq-1.5-1-a5b5cbe(Google CloudShell 上的默认值)barfs 上面带有jq: error: ARGS/0 is not defined at <top-level>, line 1: $ARGS.named jq: 1 compile error
  • 我刚刚验证它可以在 Arch Linux 上使用 jq-1.6,这是最新版本(2018 年 11 月)。
  • 如果您想要一个所有值都在顶层的单层对象,这非常有效。然而,对于某些用例(例如使用curl 与API 通信),args 可能需要更深地嵌套。在这种情况下,需要@chepner 答案的原始版本。
【解决方案2】:

如果你有 node.js 并在全局中安装了 minimist:

jc() {
    node -p "JSON.stringify(require('minimist')(process.argv), (k,v) => k=='_'?undefined:v)" -- "$@"
}
jc --key1 foo --number 12 --boolean \
    --under_score 'abc def' --'white space' '   '
# {"key1":"foo","number":12,"boolean":true,"under_score":"abc def","white space":"   "}

你可以用 curl 或什么来发布它:

curl --data "$(jc --type message --value 'hello world!')" \
    --header 'content-type: application/json' \
    http://server.ip/api/endpoint

小心极简主义者会解析点:

jc --m.room.member @gholk:ccns.io
# {"m":{"room":{"member":"@gholk:ccns.io"}}}

【讨论】:

    【解决方案3】:

    我必须想出所有可能的方法来处理命令请求中的 json 字符串,请看下面的代码,看看为什么使用不正确的单引号会失败。

    # Create Release and Tag commit in Github repository
    
    # returns string with in-place substituted variables 
    
    json=$(cat <<-END
        {
            "tag_name": "${version}", 
            "target_commitish": "${branch}", 
            "name": "${title}", 
            "body": "${notes}", 
            "draft": ${is_draft}, 
            "prerelease": ${is_prerelease} 
        }
    END
    )
    
    # returns raw string without any substitutions
    # single or double quoted delimiter - check HEREDOC specs
    
    json=$(cat <<-!"END"   # or 'END' 
        {
            "tag_name": "${version}", 
            "target_commitish": "${branch}", 
            "name": "${title}", 
            "body": "${notes}", 
            "draft": ${is_draft}, 
            "prerelease": ${is_prerelease} 
        }
    END
    )
    
    # prints fully formatted string with substituted variables as follows:
    
    echo "${json}"  
    { 
        "tag_name" : "My_tag", 
        "target_commitish":"My_branch"
        ....
    }
    
    

    注意 1:单引号和双引号的使用

    # enclosing in single quotes means no variable substitution 
    # (treats everything as raw char literals)
    
    echo '${json}'   
    ${json} 
    
    echo '"${json}"'   
    "${json}" 
    
    # enclosing in single quotes and outer double quotes causes
    # variable expansion surrounded by single quotes(treated as raw char literals).
    
    echo "'${json}'" 
    '{ 
        "tag_name" : "My_tag", 
        "target_commitish":"My_branch"
        ....
    }'
    

    注意 2:使用线路终止符时要小心

    • 请注意,json 字符串使用行终止符格式化,例如 LF \n
    • 或回车\r(如果在windows上编码则包含CRLF \r\n
    • 使用 (translate) tr shell 中的实用程序,我们可以删除行终止符(如果有)
    
    # following code serializes json and removes any line terminators 
    # in substituted value/object variables too
    
    json=$(echo "$json" | tr -d '\n' | tr -d '\r' )
    
    # string enclosed in single quotes are still raw literals
    
    echo '${json}'   
    ${json} 
    
    echo '"${json}"'   
    "${json}" 
    
    # After CRLF/LF are removed
    
    echo "'${json}'" 
    '{ "tag_name" : "My_tag", "target_commitish":"My_branch" .... }'
    

    注意 3:格式

    • 在使用变量操作 json 字符串时,我们可以使用 '" 的组合,例如,如果我们想使用外部双引号保护一些原始文字以进行适当的替换/字符串插值:
    # mixing ' and " 
    
    username=admin
    password=pass
    
    echo "$username:$password"
    admin:pass
    
    echo "$username"':'"$password"
    admin:pass
    
    echo "$username"'[${delimiter}]'"$password"
    admin[${delimiter}]pass
    
    

    注意 4:在命令中使用

    • 按照 curl 请求已经删除了现有的\n(即序列化 json)
    response=$(curl -i \
                --user ${username}:${api_token} \
                -X POST \
                -H 'Accept: application/vnd.github.v3+json' \
                -d "$json" \
                "https://api.github.com/repos/${username}/${repository}/releases" \
                --output /dev/null \
                --write-out "%{http_code}" \
                --silent
              )
    

    因此,当将其用于命令变量时,请在使用前验证其格式是否正确 :)

    【讨论】:

    • 仅代码 不鼓励在 SO 上回答。请考虑添加一个解释,突出强调重要的位以及它如何/为什么会强制解决 OP 的问题,以获得长期价值和快速理解。大多数支持是随着时间的推移而积累的,因为用户从您的帖子中学到了一些可以应用于他们自己的编码问题的东西。解释对“啊哈”学习时刻大有帮助。 Words 还有助于克服“代码墙”的犹豫,这种犹豫可能会促使用户跳到不同的帖子。
    • 感谢您指出,我已经更新了我的答案,我的问题是由于在 json 字符串中使用了不正确的单引号和双引号组合以及在使用 json 变量时。在 posix/windows bash shell 和 unix bash shell 上使用 CR/LF 时,我也遇到了问题。因此,删除换行符/回车终止符是解决我的问题的关键。很高兴做出贡献!
    • 我发现您的示例是迄今为止最容易阅读和编写的,尤其是对于更复杂的 JSON 文档。谢谢!
    【解决方案4】:

    你可以使用envsubst:

      export VAR="some_value_here"
      echo '{"test":"$VAR"}' | envsubst > json.json
    

    它也可能是一个“模板”文件:

    //json.template
    {"var": "$VALUE", "another_var":"$ANOTHER_VALUE"}
    

    所以你可以这样做之后:

    export VALUE="some_value_here"
    export ANOTHER_VALUE="something_else"
    cat  json.template | envsubst > misha.json
    

    【讨论】:

      【解决方案5】:

      这些解决方案来得有点晚,但我认为它们本质上比以前的建议更简单(避免引用和转义的复杂性)。

          BUCKET_NAME=testbucket
          OBJECT_NAME=testworkflow-2.0.1.jar
          TARGET_LOCATION=/opt/test/testworkflow-2.0.1.jar
          
          # Initial unsuccessful solution
          JSON_STRING='{"bucketname":"$BUCKET_NAME","objectname":"$OBJECT_NAME","targetlocation":"$TARGET_LOCATION"}'
          echo $JSON_STRING 
          
          # If your substitution variables have NO whitespace this is sufficient
          JSON_STRING=$(tr -d [:space:] <<JSON
          {"bucketname":"$BUCKET_NAME","objectname":"$OBJECT_NAME","targetlocation":"$TARGET_LOCATION"}
          JSON
          )
          echo $JSON_STRING 
          
          # If your substitution variables are more general and maybe have whitespace this works
          JSON_STRING=$(jq -c . <<JSON
          {"bucketname":"$BUCKET_NAME","objectname":"$OBJECT_NAME","targetlocation":"$TARGET_LOCATION"}
          JSON
          )
          echo $JSON_STRING 
          
          #... A change in layout could also make it more maintainable
          JSON_STRING=$(jq -c . <<JSON
          {
             "bucketname" : "$BUCKET_NAME",
             "objectname" : "$OBJECT_NAME",
             "targetlocation" : "$TARGET_LOCATION"
          }
          JSON
          )
          echo $JSON_STRING
      

      【讨论】:

        【解决方案6】:

        Bash 不会将变量插入单引号字符串。为了获得变量 bash 需要一个双引号字符串。 您需要对 JSON 使用双引号字符串,并在 JSON 字符串中转义双引号字符。 示例:

        #!/bin/sh
        
        BUCKET_NAME=testbucket
        OBJECT_NAME=testworkflow-2.0.1.jar
        TARGET_LOCATION=/opt/test/testworkflow-2.0.1.jar
        
        JSON_STRING="{\"bucketname\":\"$BUCKET_NAME\",\"objectname\":\"$OBJECT_NAME\",\"targetlocation\":\"$TARGET_LOCATION\"}"
        
        
        echo $JSON_STRING 
        

        【讨论】:

          【解决方案7】:

          使用 NodeJS 在 Hao's answer 的基础上构建:您可以拆分行,并使用 -p 选项,这样就不必使用 console.log

          JSON_STRING=$(node -pe "
            JSON.stringify({
              bucketname: process.env.BUCKET_NAME,
              objectname: process.env.OBJECT_NAME,
              targetlocation: process.env.TARGET_LOCATION
            });
          ")
          

          一个不便之处是您需要事先导出变量,即

          export BUCKET_NAME=testbucket
          # etc.
          

          注意:您可能会想,为什么要使用process.env?为什么不只使用单引号并使用 bucketname: '$BUCKET_NAME', 等以便 bash 插入变量?原因是使用 process.env 更安全 - 如果您无法控制 $TARGET_LOCATION 的内容,它可能会将 JavaScript 注入您的节点命令并执行恶意操作(通过关闭单引号,例如 $TARGET_LOCATION 字符串内容可以是'}); /* Here I can run commands to delete files! */; console.log({'a': 'b。另一方面,process.env 负责清理输入。

          【讨论】:

            【解决方案8】:

            Node.js开发者,或者如果你已经安装了node环境,可以试试这个:

            JSON_STRING=$(node -e "console.log(JSON.stringify({bucketname: $BUCKET_NAME, objectname: $OBJECT_NAME, targetlocation: $TARGET_LOCATION}))")
            

            此方法的优点是您可以轻松地将非常复杂的 JSON 对象(比如对象包含数组,或者如果您需要 int 值而不是字符串)转换为 JSON 字符串,而无需担心无效 json 错误。

            缺点是它依赖于Node.js 环境。

            【讨论】:

            • 不错的想法,但它在 bash 变量周围缺少某种形式的引号,这意味着节点会将它们解释为 JavaScript 变量而不是字符串(因此命令会出错)。此外,使用这样的“注入”bash 变量存在潜在的安全漏洞 - 请参阅 my answer
            【解决方案9】:

            可以通过以下方式完成:

            JSON_STRING='{"bucketname":"'$BUCKET_NAME'","objectname":"'$OBJECT_NAME'","targetlocation":"'$TARGET_LOCATION'"}'
            

            【讨论】:

              【解决方案10】:

              如果您需要构建一个 JSON 表示,其中应该省略映射到未定义或空变量的成员,那么 jo 可以提供帮助。

              #!/bin/bash
              
              BUCKET_NAME=testbucket
              OBJECT_NAME=""
              
              JO_OPTS=()
              
              if [[ ! "${BUCKET_NAME}x" = "x" ]] ; then
                      JO_OPTS+=("bucketname=${BUCKET_NAME}")
              fi
              
              if [[ ! "${OBJECT_NAME}x" = "x" ]] ; then
                      JO_OPTS+=("objectname=${OBJECT_NAME}")
              fi
              
              if [[ ! "${TARGET_LOCATION}x" = "x" ]] ; then
                      JO_OPTS+=("targetlocation=${TARGET_LOCATION}")
              fi
              
              jo "${JO_OPTS[@]}"
              

              【讨论】:

              • 从 2018 年找到这个问题及其答案。但奇怪的是,在底部是你的答案,这是你在 4 小时前写的。因此,为这个漂亮的实用程序 +1,但也为奇怪的巧合 +1。
              【解决方案11】:

              一种可能性:

              JSON_STRING='{"bucketname":"'"$BUCKET_NAME"'","objectname":"'"$OBJECT_NAME"'","targetlocation":"'"$TARGET_LOCATION"'"}'
              

              【讨论】:

              • 聪明。我总是忘记你可以混合单引号和双引号字符串。
              • jq 库示例非常适合复杂/冗长的实现,但这对于此处和此处的 1-liner 非常有用。
              • 当您需要在命令中将 JSON 字符串作为选项传递时,这尤其方便,例如 elasticdump。通过 jq 处理它可能会使它复杂化。
              【解决方案12】:

              如果您不提前知道变量的内容是否正确转义以包含在 JSON 中,您最好使用像 jq 这样的程序来生成 JSON。否则,您最终会遇到无效的 JSON 问题。

              BUCKET_NAME=testbucket
              OBJECT_NAME=testworkflow-2.0.1.jar
              TARGET_LOCATION=/opt/test/testworkflow-2.0.1.jar
              
              JSON_STRING=$( jq -n \
                                --arg bn "$BUCKET_NAME" \
                                --arg on "$OBJECT_NAME" \
                                --arg tl "$TARGET_LOCATION" \
                                '{bucketname: $bn, objectname: $on, targetlocation: $tl}' )
              

              【讨论】:

              • 极好的建议:为手头的任务使用正确的解析器/生成器。适用于 JSON、CSV、XML、...
              • 如果 jq 的 args 得到解释,对未来的读者来说可能会很好。
              • 我做错了...谢谢你的回答
              • 通常jq处理输入;但是对于-n,没有。 --arg &lt;var&gt; &lt;string&gt;$&lt;var&gt; 替换为字符串值 &lt;string&gt;--argjson &lt;var&gt; &lt;json&gt;$&lt;var&gt; 替换为 Json blob &lt;json&gt;--arg--argjson 的组合提供了构建任意 Json 结构的强大机制。
              【解决方案13】:

              你可以使用printf:

              JSON_FMT='{"bucketname":"%s","objectname":"%s","targetlocation":"%s"}\n'
              printf "$JSON_FMT" "$BUCKET_NAME" "$OBJECT_NAME" "$TARGET_LOCATION"
              

              更清晰更简单

              【讨论】:

              • 很好,特别是如果您没有或不能安装 jq。这也可以让您将其保存到变量中:JSON_STRING=$(printf "$JSON_FMT" "$BUCKET_NAME"...)
              【解决方案14】:

              首先,不要使用 ALL_CAPS_VARNAMES:很容易意外覆盖关键的 shell 变量(如 PATH)

              在 shell 字符串中混合单引号和双引号可能很麻烦。在这种情况下,我会使用printf:

              bucket_name=testbucket
              object_name=testworkflow-2.0.1.jar
              target_location=/opt/test/testworkflow-2.0.1.jar
              template='{"bucketname":"%s","objectname":"%s","targetlocation":"%s"}'
              
              json_string=$(printf "$template" "$BUCKET_NAME" "$OBJECT_NAME" "$TARGET_LOCATION")
              
              echo "$json_string"
              

              关于家庭作业,请仔细阅读本页:Security implications of forgetting to quote a variable in bash/POSIX shells


              关于使用字符串连接创建 JSON 的注意事项:存在极端情况。例如,如果您的任何字符串包含双引号,您可以破坏 JSON:

              $ bucket_name='a "string with quotes"'
              $ printf '{"bucket":"%s"}\n' "$bucket_name"
              {"bucket":"a "string with quotes""}
              

              使用 bash 更安全地执行此操作,我们需要转义该字符串的双引号:

              $ printf '{"bucket":"%s"}\n' "${bucket_name//\"/\\\"}"
              {"bucket":"a \"string with quotes\""}
              

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-02-18
              • 2021-03-30
              • 2014-02-21
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多