You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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子进程,还需要新增运行中子进程的存储和销毁逻辑,完整修改如下:

  1. 新增全局变量存储运行中的子进程(需加锁保证并发安全)
import "sync"

var (
    runningCmd *exec.Cmd
    cmdLock sync.Mutex
)
  1. 修改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()
}
  1. 修改信号处理逻辑,收到中断时先销毁子进程
// 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 20:06:03