Go语言调用cmd.Run执行bash命令时无法捕获CTRL+C信号如何解决
问题原因
默认通过exec.Command启动的子进程会与Go主进程归属同一个前台进程组,按下CTRL+C触发的SIGINT信号会发送给整个进程组内的所有进程,此时bash子进程会优先响应信号直接退出,会干扰甚至阻断Go主进程的信号处理逻辑触发。
修复方案
基础修复(仅保证主进程能捕获信号)
直接在runShellCommand中给启动的子进程配置独立进程组即可,修改后代码如下:
// Run a shell command func runShellCommand(command string) (string, string) { var shell_to_use string = "/usr/bin/bash" var stdout bytes.Buffer var stderr bytes.Buffer cmd := exec.Command(shell_to_use, "-c", command) // 新增配置:让子进程进入独立进程组,不接收发给主进程组的CTRL+C信号 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { logger.Error(stderr.String()) logger.Fatal(err) } return stdout.String(), stderr.String() }
完善修复(主动清理子进程避免孤儿进程)
如果需要在捕获到CTRL+C时同时终止正在运行的bash子进程,还需要新增运行中子进程的存储和销毁逻辑,完整修改如下:
- 新增全局变量存储运行中的子进程(需加锁保证并发安全)
import "sync" var ( runningCmd *exec.Cmd cmdLock sync.Mutex )
- 修改
runShellCommand逻辑
// Run a shell command func runShellCommand(command string) (string, string) { var shell_to_use string = "/usr/bin/bash" var stdout bytes.Buffer var stderr bytes.Buffer cmd := exec.Command(shell_to_use, "-c", command) cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} // 存储当前运行的子进程 cmdLock.Lock() runningCmd = cmd cmdLock.Unlock() // 函数退出前清空存储 defer func() { cmdLock.Lock() runningCmd = nil cmdLock.Unlock() }() cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { // 主动杀死的子进程不需要抛出致命错误 if _, isExitErr := err.(*exec.ExitError); !isExitErr || !INTERRUPT_REQUEST { logger.Error(stderr.String()) logger.Fatal(err) } } return stdout.String(), stderr.String() }
- 修改信号处理逻辑,收到中断时先销毁子进程
// Wait for all goroutines to exit before stopping func manageCleanExit() { c := make(chan os.Signal) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c INTERRUPT_REQUEST = true // 销毁正在运行的子进程及其子命令 cmdLock.Lock() if runningCmd != nil && runningCmd.Process != nil { _ = syscall.Kill(-runningCmd.Process.Pid, syscall.SIGTERM) } cmdLock.Unlock() runningGoroutinesChecker() logger.Info("Exiting") os.Exit(0) }() }
注意:以上实现仅兼容Linux、macOS等类Unix系统,Windows平台需要单独配置子进程创建参数适配。
内容的提问来源于stack exchange,提问作者dmsakl
相关产品推荐
相关产品推荐

