使用 printf %q 使带引号的字符串可用作 shell 脚本输入

Posted

技术标签:

【中文标题】使用 printf %q 使带引号的字符串可用作 shell 脚本输入【英文标题】:Using printf %q to make a quoted string usable as shell script input 【发布时间】:2019-05-06 12:01:45 【问题描述】:

在 bash 脚本中,我尝试将单引号和双引号的命令字符串附加到文件 (.profile)。

我想使用echo 然后>> 命令到.profile。当然,我愿意接受任何可行的解决方案。

我想使用的命令是 echo "curl -X POST -H "Content-Type: application/json" -d '"value1":"PHONENUMBER","value2":"MESSAGE"' https://maker.ifttt.com/trigger/TRIGGER/with/key/KEY &> /dev/null" >> .profile,但显然这在我的 bash 脚本中不起作用。

我不清楚printf %q 的工作原理,也不明白如何将其应用于我的问题。

我试过了

`CMDSTRING='curl -X POST -H "Content-Type: application/json" -d '`
`CMDSTRING=$CMDSTRING"'"`
`CMDSTRING=$CMDSTRING'"value1":"+PHONENUMBER","value2":"MESSAGE"'`
`CMDSTRING=$CMDSTRING"'"`
`CMDSTRING=$CMDSTRING' https://maker.ifttt.com/trigger/TRIGGER/with/key/KEY &> /dev/null'`

`echo $CMDSTRING`

【问题讨论】:

很高兴看到光明并以“正确”的方式去做。我以为这会又快又脏,但现在它变得缓慢、丑陋和痛苦。您建议哪种方法可行? 顺便说一句,如果您的消息来自不受信任的来源,使用 jq 或其他 JSON 语法感知工具对其进行格式化以包含在内会更安全一些;这样,包含文字 " 的消息将不会逃避其引用并能够添加额外的有效负载参数。 (我不知道 IFTTT 的 API,但如果它支持回调,这些参数甚至可能是恶意的)。 re:编辑,请参阅BashPitfalls #14,了解为什么echo $var 总是有问题(需要echo "$var")。我希望反引号只是一个格式错误;你永远不会希望它们出现在你的实际代码中。 顺便说一句,还请注意,全大写变量名称位于 shell 和 POSIX 指定实用程序本身使用的命名空间中,而小写名称保留供应用程序使用并保证不会发生冲突。 (我们遇到很多有问题的人,因为他们运行for PATH in ...,而for path in ... 符合标准的建议并且不会无意中改变shell 的运行方式)。请参阅 pubs.opengroup.org/onlinepubs/9699919799/basedefs/…,记住 shell 和环境变量共享一个命名空间。 顺便说一句,还要注意printf '%q' 生成的代码对于bash 的评估是安全的,但对于所有符合POSIX 的shell来说不一定是安全的>。这使它成为在.bash_profile 中生成内容的正确工具(如果您 实际上使用了以前未知的值),但是.profile 的错误工具(可以由bash 以外的shell 使用)。 【参考方案1】:

使用printf '%q' 生成.profile 内容如下所示:


  printf '%q ' \
    curl -X POST -H "Content-Type: application/json" \
      -d '"value1":"PHONENUMBER","value2":"MESSAGE"' \
      https://maker.ifttt.com/trigger/TRIGGER/with/key/KEY
  printf '%s\n' "&>/dev/null" 
 >> .profile

请注意,如果您希望将&>/dev/null 解析为语法,则不能使用%q 格式字符串,因为就其本质而言,它会格式化所有传递以解析为数据的内容。

因此,我们将printf '%q ' "command name" "first argument" ... 用于实际命令本身,并格式化带外重定向。


也就是说,请注意,只有当您从不受信任的来源替换变量(而不是像示例中那样对它们进行硬编码)并且担心无效值被滥用时,上述内容才有 用于命令注入。如果您真的只是在文件末尾附加一个常量字符串,引用的 heredoc 将让您手动构建更自然的 shell 引用(实际上,正如您已经完成的那样!),并逐字传递:

cat >>.profile <<'EOF'
curl -X POST -H "Content-Type: application/json" \
  -d '"value1":"PHONENUMBER","value2":"MESSAGE"' \
  https://maker.ifttt.com/trigger/TRIGGER/with/key/KEY &> /dev/null
EOF

这里,&lt;&lt;'EOF'EOF 之间的所有内容都通过完全原样传递,包括引号和 shell 可能尝试解释的参数扩展。

【讨论】:

谢谢。让我试试看。 CAPS 中的值对于这篇文章来说是模糊的,但实际上是常量。这是我为研究生网络安全学生建立的实验室的一部分,因此它是一个封闭的、知名的小组。他们需要找到这种妥协。 如果一切都是不变的,那么根本没有理由使用printf %q;其目的是获取仅动态已知的内容,并找到一种方法将它们格式化为不能被解释为代码的文字。如果您以熟悉语言语法的人的身份编写这些文字,您已经可以防止它们被手动解析为非文字。 考虑到这一点,是否有更简单的方法将命令字符串附加到 .profile 文件? 这是我在答案后半部分展示的heredoc方法。

以上是关于使用 printf %q 使带引号的字符串可用作 shell 脚本输入的主要内容,如果未能解决你的问题,请参考以下文章

格式化输出(fmt包)

使用printf而不必转义双引号?

ruby 中%Q %q %W %w %x %r %s的用法

Oracle单引号转义符

引号替换 前位输出

visual studio c/c++报错:向“printf”传递了额外参数: _Param_ 未由格式字符串使用