使用 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"
我需要将这些 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"}}}
除了
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"