Bash中从数组元素构建命令并判断退出码的问题解析
最小示例
#!/bin/bash testFunc() ( shopt -s extglob local posArgs=(); for((i=1; i<="$#"; ++i)) do posArgs+("${!i}"); done #<取消注释查看> echo "${posArgs[@]:0:$((${#posArgs[@]} - 1))}"; if ${posArgs[@]:0:$((${#posArgs[@]} - 1))}; then return 0; fi return 1; )
- 函数体使用
()而非{},目的是限制shopt的作用域 - 函数逻辑:丢弃最后一个参数,执行剩余参数对应的命令并检查退出码——命令退出码为0则函数返回0,否则返回1;刻意未使用引号和命令替换
可行场景
以下调用能正常运行:
testFunc "echo" "whatever" "discardThis"; # 输出 whatever testFunc "pidof" "memcached" "discardThis"; # 输出 memcached 的进程ID(示例值:12435) testFunc "pwd" "discardThis"; # 输出当前工作路径 testFunc "/bin/bash" "-c" "pwd" "discardThis"; # 输出当前工作路径 testFunc "/bin/bash" "-c" "pwd;pwd" "discardThis"; # 连续输出两次当前路径(注意:;和pwd之间无空格)
不可行场景
以下调用会报错或不符合预期:
testFunc "/bin/bash" "-c" "[[ 2 -eq 2 ]]" "discardThis"; # 报错 testFunc "/bin/bash" "-c" "\"[[ 2 -eq 2 ]]\"" "discardThis"; # 报错 testFunc "/bin/bash" "-c" "\"echo helloo\"" "discardThis"; # 报错 testFunc "/bin/bash" "-c" "echo helloo" "discardThis"; # 退出码为0但无输出 testFunc "/bin/bash" "-c" "pwd; pwd" "discardThis"; # 仅输出一次当前路径 testFunc "/bin/bash" "-c" "\"pwd; pwd\"" "discardThis"; # 报错
上述场景的错误信息类似:helloo": -c: line 0: unexpected EOF while looking for matching...
有意思的是,传入eval可以解决这些问题:
testFunc "eval" "/bin/bash" "-c" "\"echo helloo\"" "discardThis"; # 输出 helloo testFunc "eval" "/bin/bash" "-c" "\"[[ 2 -eq 2 ]]\"" "discardThis"; # 退出码为0 testFunc "eval" "/bin/bash" "-c" "pwd; echo haha; pwd;" "discardThis"; # 依次输出当前路径、haha、当前路径 testFunc "eval" "/bin/bash" "-c" "pwd; pwd" "discardThis"; # 正确输出两次当前路径
核心原因
问题本质在于bash的参数传递与命令解析机制不匹配:
bash -c的参数规则:bash -c要求后面紧跟的第一个参数是完整的命令字符串,剩余参数会作为子shell的$0、$1等位置参数。但你的函数中执行${posArgs[@]:0:$((${#posArgs[@]} - 1))}时未加引号,会把数组元素拆分成独立单词。比如testFunc "/bin/bash" "-c" "echo helloo" "discardThis"展开后实际是:/bin/bash -c echo helloo此时
bash -c的命令字符串是echo,helloo变成了子shell的$0,echo没有可输出的参数,自然无内容输出。带引号的命令字符串为何失效:当你传入
"/bin/bash" "-c" "\"echo helloo\""时,函数展开后是:/bin/bash -c "\"echo helloo\""此时
bash -c收到的命令字符串是"echo helloo"(包含外层双引号),bash会把这个带引号的字符串当作一个整体命令去查找,而非解析成echo helloo执行,自然会出现"找不到命令"或引号匹配错误。eval的作用:eval会将传入的参数重新拼接成完整命令字符串,再重新解析执行。比如testFunc "eval" "/bin/bash" "-c" "\"echo helloo\""展开后是:eval /bin/bash -c "\"echo helloo\""eval会把参数拼接成/bin/bash -c "echo helloo",此时bash -c能正确识别命令字符串并执行。
内容的提问来源于stack exchange,提问作者ibrahim tanyalcin

