在 bash 中转义字符(对于 JSON)

我使用 git,然后将提交消息和其他位作为 JSON 有效负载发送到服务器。

目前我有:

MSG=`git log -n 1 --format=oneline | grep -o ' .\+'`

它将味精设置为:

Calendar can't go back past today

那么

curl -i -X POST \
-H 'Accept: application/text' \
-H 'Content-type: application/json' \
-d "{'payload': {'message': '$MSG'}}" \
'https://example.com'

我真正的 JSON 还有两个字段。

这样可以很好地工作,但是当我有一个提交消息,比如上面带有一个撇号的提交消息时,JSON 是无效的。

如何转义 bash 中所需的字符?我不熟悉这门语言,所以不知道从何说起。用 \'代替 '至少可以完成这项工作,我怀疑。

94595 次浏览

我发现了类似的东西:

MSG=`echo $MSG | sed "s/'/\\\\\'/g"`

好了,知道该怎么做了。Bash 像预期的那样本地支持这一点,尽管一如既往,语法实际上并不容易猜测!

本质上,${string//substring/replacement}返回您想要的图像,因此您可以使用

MSG=${MSG//\'/\\\'}

下一个问题是第一个正则表达式不再工作了,但是可以用

git log -n 1 --pretty=format:'%s'

最后,我甚至不需要逃避他们。相反,我只是将 JSON 中的所有’内容交换为”。你每天都能学到东西。

当我遇到这个问题时,我还试图在 Bash 中转义字符,以便使用 JSON 进行传输。我发现实际上有一个更大的 必须转义的字符列表 & nash; ,特别是如果您试图处理自由格式的文本。

我发现有两个建议很有用:

  • 使用此线程中描述的 Bash${string//substring/replacement}语法。
  • 对制表符、换行符、回车符等使用实际的控制字符。在 vim 中,您可以输入 Ctrl + V,然后输入实际的控制代码(例如,用于 tab 的 Ctrl + I)。

我想到的 Bash 替代品如下:

JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\\/\\\\} # \
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\//\\\/} # /
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\'/\\\'} # ' (not strictly needed ?)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//\"/\\\"} # "
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//   /\\t} # \t (tab)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//
/\\\n} # \n (newline)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^M/\\\r} # \r (carriage return)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^L/\\\f} # \f (form feed)
JSON_TOPIC_RAW=${JSON_TOPIC_RAW//^H/\\\b} # \b (backspace)

在这个阶段,我还没有想出如何正确地转义 Unicode 字符,这显然也是必需的。如果我解决了这个问题,我会更新我的答案。

与其担心如何正确地引用数据,不如将它保存到一个文件中,并使用 curl允许的 @结构和 --data选项。为了确保 git的输出被正确转义以用作 JSON 值,可以使用类似 jq的工具来生成 JSON,而不是手动创建它。

jq -n --arg msg "$(git log -n 1 --format=oneline | grep -o ' .\+')" \
'{payload: { message: $msg }}' > git-tmp.txt


curl -i -X POST \
-H 'Accept: application/text' \
-H 'Content-type: application/json' \
-d @git-tmp.txt \
'https://example.com'

您还可以使用 -d @-直接从标准输入读取; 我将这留给读取器作为一个练习来构造从 git读取并生成用 curl上载的正确有效负载消息的管道。

(提示: 这是 jq ... | curl ... -d@- 'https://example.com')

使用 Python:

这个解决方案不是纯 bash,但是它是非侵入性的,可以处理 unicode。

json_escape () {
printf '%s' "$1" | python -c 'import json,sys; print(json.dumps(sys.stdin.read()))'
}

注意,JSON 是标准 python 库的一部分,并且已经存在很长时间了,所以这是一个非常小的 python 依赖。

或者使用 PHP:

json_escape () {
printf '%s' "$1" | php -r 'echo json_encode(file_get_contents("php://stdin"));'
}

使用方法如下:

$ json_escape "ヤホー"
"\u30e4\u30db\u30fc"

最简单的方法是使用 < strong > jshon ,即 用于解析、读取和创建 JSON 的命令行工具

jshon -s 'Your data goes here.' 2>/dev/null

git log -n 1 --format=oneline | grep -o ' .\+' | jq --slurp --raw-input

以上行对我有用。参考 有关更多 jq工具的 https://github.com/stedolan/jq

我有同样的想法,在提交之后使用提交消息发送消息。 首先我尝试相似是作为这里的作者。 但后来找到了一个更好更简单的解决方案。

刚刚创建了 php 文件,它发送消息并用 wget 调用它。 挂钩/接收:

wget -qO - "http://localhost/git.php"

译者:

chdir("/opt/git/project.git");
$git_log = exec("git log -n 1 --format=oneline | grep -o ' .\+'");

然后创建 JSON 并以 PHP 风格调用 CURL

这是一个使用 Perl 转义的解决方案,它将反斜杠(\)、双引号(")和控制字符 U+0000转义为 U+001F:

$ echo -ne "Hello, 🌵\n\tBye" | \
perl -pe 's/(\\(\\\\)*)/$1$1/g; s/(?!\\)(["\x00-\x1f])/sprintf("\\u%04x",ord($1))/eg;'
Hello, 🌵\u000a\u0009Bye

jq 可以做到这一点。

轻量级,免费,并用 C 编写,jq享有广泛的社区支持,超过15000颗星在 GitHub。我个人发现它在我的日常工作中非常快速和有用。

将字符串转换为 JSON

echo -n '猫に小判' | jq -Rsa .
# "\u732b\u306b\u5c0f\u5224"

解释一下,

  • -R的意思是“原始输入”
  • -s表示“包括换行符”(助记符: “ slurp”)
  • -a表示“ ascii 输出”(可选)
  • .表示“输出 JSON 文档的根”

Git + Grep 用例

要修复 OP 给出的代码示例,只需通过 jq 进行管道传输。

MSG=`git log -n 1 --format=oneline | grep -o ' .\+' | jq -Rsa .`

我也遇到过同样的问题。我试图在 bash 中的 cURL 的有效负载上添加一个变量,但它一直返回的是 void _ JSON。在尝试了很多逃避技巧之后,我找到了一个简单的方法来解决我的问题。答案都在单引号和双引号中:

 curl --location --request POST 'https://hooks.slack.com/services/test-slack-hook' \
--header 'Content-Type: application/json' \
--data-raw '{"text":'"$data"'}'

也许对某些人来说很有用!

[ ... ]带有一个撇号的 JSON 是无效的。

根据 https://www.json.org,JSON 字符串中允许单引号。

如何转义 bash 中所需的字符?

您可以使用 来正确地准备您想要发布的 JSON。
由于无法测试 https://example.com,我将使用 https://api.github.com/markdown(参见 这个答案)作为示例。

让我们假设 'çömmít' "mêssågè"git log -n 1 --pretty=format:'%s'的奇异输出。

使用 "text"属性的值正确转义创建(序列化) JSON 对象:

$ git log -n 1 --pretty=format:'%s' | \
xidel -se 'serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})'
{"text":"'\u00E7\u00F6mm\u00EDt' \"m\u00EAss\u00E5g\u00E8\""}

卷曲(变量)

$ eval "$(
git log -n 1 --pretty=format:'%s' | \
xidel -se 'msg:=serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})' --output-format=bash
)"


$ echo $msg
{"text":"'\u00E7\u00F6mm\u00EDt' \"m\u00EAss\u00E5g\u00E8\""}


$ curl -d "$msg" https://api.github.com/markdown
<p>'çömmít' "mêssågè"</p>

卷(管)

$ git log -n 1 --pretty=format:'%s' | \
xidel -se 'serialize({"text":$raw},{"method":"json","encoding":"us-ascii"})' | \
curl -d@- https://api.github.com/markdown
<p>'çömmít' "mêssågè"</p>

实际上,如果您已经在使用 xidel,就不需要使用 curl

西德尔(烟斗)

$ git log -n 1 --pretty=format:'%s' | \
xidel -s \
-d '{serialize({"text":read()},{"method":"json","encoding":"us-ascii"})}' \
"https://api.github.com/markdown" \
-e '$raw'
<p>'çömmít' "mêssågè"</p>

Xidel (管道,在查询中)

$ git log -n 1 --pretty=format:'%s' | \
xidel -se '
x:request({
"post":serialize(
{"text":$raw},
{"method":"json","encoding":"us-ascii"}
),
"url":"https://api.github.com/markdown"
})/raw
'
<p>'çömmít' "mêssågè"</p>

希德尔(全部在查询中)

$ xidel -se '
x:request({
"post":serialize(
{"text":system("git log -n 1 --pretty=format:'\''%s'\''")},
{"method":"json","encoding":"us-ascii"}
),
"url":"https://api.github.com/markdown"
})/raw
'
<p>'çömmít' "mêssågè"</p>

在您的环境中集成一个 JSON 感知工具有时是不可行的,所以 这里有一个适用于所有 UNIX/Linux 的 POSIX 解决方案:

json_stringify() {
[ "$#" -ge 1 ] || return 1
LANG=C awk '
BEGIN {
for ( i = 1; i <= 127; i++ )
repl[ sprintf( "%c", i) ] = sprintf( "\\u%04x", i )


for ( i = 1; i < ARGC; i++ ) {
s = ARGV[i]
printf("%s", "\"")


while ( match( s, /[\001-\037\177"\\]/ ) ) {
printf("%s%s", \
substr(s,1,RSTART-1), \
repl[ substr(s,RSTART,RLENGTH) ] \
)
s = substr(s,RSTART+RLENGTH)
}
print s "\""
}
exit
}
' "$@"
}

或者使用广泛使用的 perl:

json_stringify() {
[ "$#" -ge 1 ] || return 1
LANG=C perl -le '
for (@ARGV) {
s/[\x00-\x1f\x7f"\\]/sprintf("\\u%04x",ord($0))/ge;
print "\"$_\""
}
' -- "$@"
}

然后你可以做:

json_stringify '"foo\bar"' 'hello
world'
"\u0022foo\bar\u0022"
"hello\u000aworld"

限制:

  • 不能处理 NUL字节。

  • 不验证 UNICODE 的输入,它只转义 RFC 8259中指定的强制 ASCII 字符。


回答 OP 的问题:

MSG=$(git log -n 1 --format=oneline | grep -o ' .\+')


curl -i -X POST \
-H 'Accept: application/text' \
-H 'Content-type: application/json' \
-d '{"payload": {"message": '"$(json_stringify "$MSG")"'}}' \
'https://example.com'