
在 go 中通过 os/exec.command 调用 /bin/sh -c 执行多命令(如 pwd && ls)时,常见错误是误将 shell 字符串中的外层引号也传入 -c 参数,导致 shell 将整个带引号的字符串当作单一命令名解析,从而报错 exit code 127。正确做法是直接传递未加引号的命令字符串。
Go 的 os/exec 包不会经过用户终端的 Shell 解析层,而是直接构造进程参数列表。当你写:
exec.Command("/bin/sh", "-c", `"pwd && pwd"`)实际等价于在系统中执行:
/bin/sh -c '"pwd && pwd"'
此时 /bin/sh 会尝试查找一个名为 "pwd && pwd"(含双引号)的可执行文件,自然失败(errno 127:command not found)。
✅ 正确方式是:去掉手动添加的外层引号,让命令字符串本身不包含引号,由 /bin/sh 自行解析其内部语法(如 &&、|、;、变量替换等):
package main
import (
"fmt"
"os/exec"
)
func main() {
fmt.Printf("-- Test 1 --\n")
cmd1 := exec.Command("/bin/sh", "-c", "pwd") // ✅ 简单命令,无引号
output1, err1 := cmd1.CombinedOutput()
if err1 != nil {
fmt.Printf("error: %v\n", err1)
return
}
fmt.Printf("%s", output1)
fmt.Printf("-- Test 2 --\n")
cmd2 := exec.Command("/bin/sh", "-c", "pwd && pwd") // ✅ 多命令,无额外引号
output2, err2 := cmd2.CombinedOutput()
if err2 != nil {
fmt.Printf("error: %v\n", err2)
return
}
fmt.Printf("%s", output2)
}⚠️ 注意事项:
- 不要对 -c 后的命令字符串做 Go 层面的引号包裹(如 fmt.Sprintf("%s", cmd)),这会破坏 shell 解析逻辑;
- 若命令中含变量、空格或特殊字符(如 ls "$HOME"),应确保字符串本身已正确转义——Go 字符串字面量需用反斜杠转义(如 "ls \"$HOME\""),而非依赖外层引号;
- 避免拼接不可信输入(如用户输入)到命令字符串中,否则存在 shell 注入风险;优先使用参数化方式(如 exec.Command("ls", path))或显式调用 sh -c 并严格校验/转义输入;
- 如需兼容不同 shell 行为,可显式指定解释器(如 /bin/bash)并确认目标环境存在。
总结:/bin/sh -c 的第二个参数是「要执行的 shell 代码字符串」,不是「要执行的命令名」——它应是纯文本脚本内容,无需、也不应被额外引号包裹。理解 Go 进程启动机制与 Shell 解析层级的差异,是写出健壮命令执行逻辑的关键。









