Eval可正常执行命令,但命令替换($())执行失败的原因解析
问题描述
以下是一段Shell脚本:
#!/bin/sh echo "intented command:" jq -n --argjson bar '{"test":"asdf"}' '$bar | .test' foo="{\"test\":\"asdf\"}" command="jq -n --argjson bar '$foo' '\$bar | .test'" echo "Built command:" echo $command echo "Command execution with eval:" eval $command echo "Command execution with command substitution:" $($command)
脚本中第一段直接执行的jq命令可正常运行;echo输出的构建命令和第一段原命令完全一致。但使用eval $command执行正常,使用$($command)执行时却报错:
jq: invalid JSON text passed to --argjson Use jq --help for help with command-line options, or see the jq manpage, or online docs at https://stedolan.github.io/jq
原因分析
核心差异在于Shell对两种执行方式的解析逻辑完全不同:
eval $command的解析过程:eval会将变量command中的内容当作完整Shell命令重新解析一遍。此时Shell会识别字符串中的单引号语法:'{"test":"asdf"}'会被处理为去掉单引号的纯JSON字符串{"test":"asdf"},正确传递给jq的--argjson参数;'$bar | .test'会被处理为去掉单引号的表达式$bar | .test,作为jq的过滤参数。整个命令的参数拆分完全符合预期。
$($command)的解析过程:$()是命令替换语法,执行流程是:- 先展开
$command得到完整字符串:jq -n --argjson bar '{"test":"asdf"}' '$bar | .test'; - Shell会对这个字符串进行分词处理,但不会解析其中的单引号语法——单引号会被当作普通字符保留在参数中。
最终拆分出的参数会变成:jq、-n、--argjson、bar、'{"test":"asdf"}'(带单引号的字符串)、'$bar、|、.test'。
这就导致jq收到的--argjson参数是带单引号的'{"test":"asdf"}',不符合JSON格式,因此报错。
- 先展开
正确的做法
不要用字符串存储命令,而是用数组保存命令和参数,Shell会自动正确处理每个参数的边界:
#!/bin/sh echo "intented command:" jq -n --argjson bar '{"test":"asdf"}' '$bar | .test' foo='{"test":"asdf"}' # 用数组存储命令及参数 command=(jq -n --argjson bar "$foo" '$bar | .test') echo "Built command:" echo "${command[@]}" # 正确执行数组中的命令 echo "Command execution with array:" "${command[@]}"
数组方式可以避免所有转义和引号解析的问题,是Shell中存储复杂命令的标准做法。
内容的提问来源于stack exchange,提问作者Agyss
相关产品推荐
相关产品推荐

