使用 Bash 个变量构建一个 JSON 字符串

Build a JSON string with Bash variables

我需要将这些 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 

首先,不要使用 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\""}

您可以使用 printf:

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

更加清晰和简单

一种可能性:

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

你最好使用像 jq 这样的程序来生成 JSON,如果你事先不知道变量的内容是否被正确转义以包含在 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 表示,其中应省略映射到未定义或空变量的成员,那么 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[@]}"

可以通过以下方式完成:

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

对于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环境。

使用 NodeJS 在 的基础上构建:您可以拆分行,并使用 -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 负责清理输入。

Bash 不会将变量插入到 single-quote 字符串中。为了获得变量 bash 需要一个 double-quote 字符串。 您需要为 JSON 使用 double-quote 字符串,并在 JSON 字符串中转义 double-quote 个字符。 示例:

#!/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 

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

    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

您可以使用 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

我不得不想出所有可能的方法来处理命令请求中的 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
  • 或回车 return \r(如果它在 windows 上编码它包含 CRLF \r\n
  • 使用 shell 中的 (translate) tr 实用程序,我们可以删除行终止符(如果有的话)

# 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 字符串时,我们可以使用 '" 的组合,如下所示,如果我们想使用外部双引号保护一些原始文字到位 substirution/string 插值:
# 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
          )

所以当把它用于命令变量时,在使用之前验证它的格式是否正确:)

如果你有 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":"   "}

你可以 post 使用 curl 或什么:

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

注意 minimist 会解析点:

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

除了 之外,还可以使用这个简单的方法完全从 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可以原样输出

将其用于 AWS Macie 配置:

JSON_CONFIG=$( jq -n \
   --arg bucket_name "$BUCKET_NAME" \
   --arg kms_key_arn "$KMS_KEY_ARN" \
   '{"s3Destination":{"bucketName":$bucket_name,"kmsKeyArn":$kms_key_arn}}'
)

aws macie2 put-classification-export-configuration --configuration "$JSON_CONFIG"