
在 go 中使用 `exec.command` 调用 `diff` 时,程序因 `diff` 返回非零退出码(如文件不同返回 1)而报错,但这并非运行失败,而是 `diff` 的正常行为;需区分“执行失败”与“语义性差异结果”。
diff 命令的设计逻辑与其他工具(如 cat 或 wc)不同:它将「文件内容相同」视为成功(退出码 0),而「存在差异」或「文件不可读」分别返回 1 和 2。Go 的 exec.Command(...).Output() 默认将任何非零退出码视为错误(*exec.ExitError),因此即使 diff -u a b 成功生成了标准输出,只要文件不一致,Go 就会触发 err != nil —— 这容易被误判为程序崩溃。
要正确处理,关键在于:
✅ 区分两类错误:
- *exec.ExitError:命令已执行,仅因语义原因(如差异存在、文件缺失)退出;
- 其他错误(如 exec.ErrNotFound、I/O 错误):命令根本未启动,属真正异常,需中止。
✅ 使用 CombinedOutput() 替代 Output():同时捕获 stdout 和 stderr,便于调试(例如当某文件不存在时,stderr 会输出 No such file or directory)。
以下是推荐的健壮实现:
package main
import (
"fmt"
"log"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("diff", "-u", "/tmp/revision-1", "/tmp/revision-4")
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// 检查具体退出状态
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
code := status.ExitStatus()
switch code {
case 0:
fmt.Println("✅ 文件完全相同")
case 1:
fmt.Println("⚠️ 文件存在差异(正常行为)")
fmt.Println(string(output)) // 输出 unified diff 内容
case 2:
log.Fatalf("❌ diff 执行失败(如文件不存在、权限不足): %s", string(output))
default:
log.Fatalf("❌ 未知退出码 %d: %s", code, string(output))
}
} else {
log.Fatalf("❌ 无法解析系统退出状态: %v", err)
}
} else {
// 非 ExitError,例如命令未找到、路径错误等
log.Fatalf("❌ 命令执行异常: %v", err)
}
} else {
// exit code == 0,无差异
fmt.Println("✅ 文件完全相同")
}
}注意事项:
- syscall.WaitStatus 在 Linux/macOS 下有效,但 Windows 不兼容(需用 exitErr.ExitCode());若需跨平台,请用 runtime.GOOS 分支处理;
- 确保待比较文件路径真实存在且有读取权限,否则 diff 会以退出码 2 报错;
- 若仅需判断是否相等(不关心 diff 内容),可改用 os.SameFile 或 bytes.Equal(io.ReadAll(f1), io.ReadAll(f2)),更高效且无 shell 依赖。
总结:diff 的退出码是其接口契约的一部分,而非错误信号。Go 的 exec 包严格遵循 POSIX 语义,开发者需主动解包 ExitError 并按业务逻辑解读退出码,而非一概 log.Fatal。










