使用 Bash 变量构建 JSON 字符串

我需要将这些 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
169170 次浏览

首先,不要使用 ALL _ CAPS _ VARNAMES: 很容易意外地覆盖一个关键的 shell 变量(如 PATH)

在 shell 字符串中混合使用单引号和双引号可能很麻烦:

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"

家庭作业,请仔细阅读这一页: 忘记在 bash/POSIX shell 中引用变量的安全隐患


关于使用字符串连接创建 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"

简单明了多了

一种可能性:

#!/bin/bash


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


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


# multi-line
JSON_STRING="{
\"bucketname\":\"${BUCKET_NAME}\",
\"objectname\":\"${OBJECT_NAME}\",
\"targetlocation\":\"${TARGET_LOCATION}\"
}"


# [optional] validate the string is valid json
echo "${JSON_STRING}" | jq

如果您事先不知道变量的内容是否被正确转义以包含在 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 表示,其中映射到未定义或空变量的成员应该被省略,那么 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 Developer,或者如果已经安装了节点环境,您可以尝试这样做:

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

这种方法的优点是可以很容易地将非常复杂的 JSON 对象(如对象包含数组,或者如果需要 int 值而不是 String)转换为 JSON String,而不用担心无效的 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 不会将变量插入到单引号字符串中。 您需要对 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

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

    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等行结束符进行格式化
  • 或回车返回 \r(如果它在窗口上编码,它包含 CRLF \r\n)
  • 使用(翻译) 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 字符串时,我们可以使用 '"的组合,如下所示,如果我们想使用外部双引号来保护一些原始文字,以便适当地使用替换/字符串插值:
# 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 并且在 global 中安装了 minist:

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 --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"}}}

除了 Chepner 的回答之外,还可以用下面这个简单的方法完全从 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"

对于使用 任意输入从 bash 构建 JSON 的一般情况,以前的许多响应(甚至是使用 jq的高投票率响应)在变量包含 "双引号或 \n换行转义字符串时省略了大小写,并且需要复杂的输入字符串串联。

在使用 jq时,您需要先输入 printf %b,以便将 \n转换为真正的换行,这样一旦您通过 jq,您将得到返回的 \n,而不是 \\n

我发现,如果你熟悉 javascript/nodejs,使用 与 nodejs 版本可以很容易地推断出这一点:

TITLE='Title'
AUTHOR='Bob'
JSON=$( TITLE="$TITLE" AUTHOR="$AUTHOR" node -p 'JSON.stringify( {"message": `Title: ${process.env.TITLE}\n\nAuthor: ${process.env.AUTHOR}`} )' )

由于使用了 process.env.,因此有点冗长,但是它允许正确地从 shell 传递变量,然后以安全的方式格式化(nodejs)反勾中的内容。

产出:

printf "%s\n" "$JSON"
{"message":"Title: Title\n\nAuthor: Bob"}

(注意: 当有一个带 ABC0的变量时,始终使用 ABC1而不是 echo "$VAR",其输出依赖于平台! 详情请参阅此处)

jq的情况也差不多

TITLE='Title'
AUTHOR='Bob'
MESSAGE="Title: ${TITLE}\n\nAuthor: ${AUTHOR}"
MESSAGE_ESCAPED_FOR_JQ=$(printf %b "${MESSAGE}")
JSON=$( jq '{"message": $jq_msg}' --arg jq_msg "$MESSAGE_ESCAPED_FOR_JQ" --null-input --compact-output --raw-output --monochrome-output )

(最后两个参数在子 shell 中运行时不是必需的,但是我只是添加了它们,这样当您在顶级 shell 中运行 jq命令时输出是相同的)。