最近我用命令行工具来测试 rpc 服务,因为此命令行工具要求输入数据是 json 格式,所以免不了要在 shell 环境构造一些 json 字符串:
shell> echo '{"content": "$(base64 foo.docx)", "type": "docx"}'
如上,我想把文件 foo.docx 的内容通过 base64 编码,然后放到 json 字符串里,但是它并不能正常工作,因为它是一个单引号字符串,命令在单引号里的是不能展开的,那换成双引号可不可以?当然可以,但是因为 json 本身包含很多双引号,所以免不了转义:
shell> echo "{\"content\": \"$(base64 foo.docx)\", \"type\": \"docx\"}"
不瞒大家说,我最开始写出如上代码的时候,脑瓜子嗡嗡的,好在最后我想到了一个绝妙的解决方法:既然用双引号字符串不可避免会带来转义问题,那么就放弃双引号字符串,而是使用单引号字符串,然后把里面的命令用单引号包起来:
shell> echo '{"content": "'$(base64 foo.docx)'", "type": "docx"}'
为什么这样可以?其实如上单引号字符串实际上是三个字符串,分别是:
- 「'{“content”: “‘」
- 「$(base64 foo.docx)」
- 「'”, “type”: “docx”}’」
与其说是用单引号把命令包起来,倒不如说是用单引号把命令隔离出来,有点四两拨千斤的感觉,脑瓜子再也不会嗡嗡的了,整个世界清静了…
因為在 bash 裡構造 json 要處理很多跳脫很麻煩,有人做了 jo 這個工具用來產生 json: https://github.com/jpmens/jo 。
我之前也自己寫了一個腳本來簡單產生 json: https://github.com/GHolk/loco/blob/abfedad/bin/jc.sh ,是用 node.js 的內建函數做的,但包裝很簡陋,沒有處理 injection 問題。
以前我也很模糊这个引号规则,直到后来读了 https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Quote-Removal 这一段,明白了 shell 中的引号对它来说其实并无实际意义,会被移除。
我更喜欢这么处理:
echo ‘{“content”: “$DATA”, “type”: “docx”}’ | DATA=$(base64 -w 0 foo.docx) envsubst
👍