Shane Xu's Home

Life is too short for so much sorrow.

How to escape single-quotes within single-quoted strings in bash

事情的始末是这样的。那天我部署了一个 Kapacitor ,然后尝试调用它的rest api添加一个Task。然而令我没想到是,习惯了使用 curl 的我,这次竟然迟迟没有搞定这个这个rest接口。

Kapacitor的这个接口其实很简单,就是往 /kapacitor/v1/tasks 这个地址 POST 一段JSON数据。然而这个JSON数据中有一个要命的 script 字段,这个字段中又有单引号出现。我用的命令如下。

curl -X POST -H "Content-Type: application/json" -d '
{
    "id" : "quote1",
    "type" : "stream",
    "dbrps": [{"db": "udp", "rp" : "autogen"}],
    "script": "stream|from().measurement(\'quote\')|alert().crit(lambda: \"price\" < 174).log(\'/tmp/price1.log\')",
    "status": "enabled"
}
' "http://127.0.0.1:9092/kapacitor/v1/tasks"

我想当然地认为,在单引号中出现单引号就用 \ 转义,然而我错了。

./test.sh: line 8: syntax error near unexpected token `)'
./test.sh: line 8: `    "script": "stream|from().measurement(\'quote\')|alert().crit(lambda: \"price\" < 174).log(\'/tmp/price1.log\')",'

于是我用 Google 搜索, 本文的标题。得到了一个解决方案,调整后的脚本如下。

curl -X POST -H "Content-Type: application/json" -d '
{
    "id" : "quote1",
    "type" : "stream",
    "dbrps": [{"db": "udp", "rp" : "autogen"}],
    "script": "stream|from().measurement('"'"'quote'"'"')|alert().crit(lambda: \"price\" < 174).log('"'"'/tmp/price1.log'"'"')",
    "status": "enabled"
}
' "http://127.0.0.1:9092/kapacitor/v1/tasks"

然而仔细一想,这好像哪里不对 measurement('"'"'quote 此处的一坨引号,实际是将原本的一个长串断开成了 '...measurement('"'"'quote...' ,所以严格意义上这并不算 escape ,并且这很丑。

然后我在这篇 http://unix.stackexchange.com/questions/187651/how-to-echo-single-quote-when-using-single-quote-to-wrap-special-characters-in 找到了另外的一些解决方案。

echo $'It\'s Shell Programming'  # ksh, bash, and zsh only, does not expand variables
echo 'It'\''s Shell Programming' # all shells, single quote is outside the quotes

这里的第二种方法,实际上和前面的说的那种是类似的,这是是把这个字符串,拆成了 'It'\''s Shell Programming' 三部分。重点说一下第一个方法。

bashManual 里有这么一段话:

Words of the form $’string’ are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows:

\a

alert (bell)

\b

backspace

\e

\E

an escape character

\f

form feed

\n

new line

\r

carriage return

\t

horizontal tab

\v

vertical tab


backslash

\’

single quote

\“

double quote

\nnn

the eight-bit character whose value is the octal value nnn (one to three digits)

\xHH

the eight-bit character whose value is the hexadecimal value HH (one or two hex digits)

\cx

a control-x character The expanded result is single-quoted, as if the dollar sign had not been present.

所以在单引号前面加个 $ 这样的字符串,可以理解为是一个类似C语言中的字符串。于是之前的命令就可以改成下面的样子了。

curl -X POST -H "Content-Type: application/json" -d $'
{
    "id" : "quote1",
    "type" : "stream",
    "dbrps": [{"db": "udp", "rp" : "autogen"}],
    "script": "stream|from().measurement(\'quote\')|alert().crit(lambda: \\"price\\" < 174).log(\'/tmp/price1.log\')",
    "status": "enabled"
}
' "http://127.0.0.1:9092/kapacitor/v1/tasks"

没想到在这个简单的问题上我花费了那么多时间。所以要经常 RTFM 啊。

Comments

comments powered by Disqus