Golang中如何捕获exec.Command的CTRL-C信号

Golang中如何捕获exec.Command的CTRL-C信号 你好,

我只是Go语言的初学者。请别太严格 🙂

我们正在使用AWS。我试图构建一个包装程序来调用"aws ssm session-start --target [instance-id]"。我们的目标是使用"aws ssm"在实例上获取交互式shell。

我写了一个函数:

func commandRun(instanceId string) error {
  cmd := exec.Command("aws", "ssm", "start-session", "--target", instanceId)
  cmd.Stderr = os.Stderr
  cmd.Stdout = os.Stdout
  cmd.Stdin = os.Stdin

  err := cmd.Run()

  if err != nil{
	fmt.Println(err)
  }
  
  return nil
}

使用这个函数,我可以连接到服务器,获取shell并运行一些命令。没有问题。但是,如果我使用像"top"这样的命令,需要通过"Ctrl-C"退出时,就会出现问题。使用"Ctrl-C"我不仅退出了"top",还退出了整个Go程序。因此我想我可以捕获"Ctrl-C"并让Go包装器本身忽略它。我了解到通常对于这类问题,你会使用一些goroutine来运行cmd,并使用一些通道来捕获"Ctrl-C"。但出于我们的目的,我不能使用这样的解决方案,因为如果cmd.Run()在goroutine中运行,我们就无法看到交互式shell。

你有什么解决这个问题的想法吗?

非常感谢!


更多关于Golang中如何捕获exec.Command的CTRL-C信号的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何捕获exec.Command的CTRL-C信号的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理exec.Command的CTRL-C信号时,需要将信号传递给子进程而不是让Go程序终止。可以通过信号处理来实现这一点。以下是修改后的代码示例:

package main

import (
    "fmt"
    "os"
    "os/exec"
    "os/signal"
    "syscall"
)

func commandRun(instanceId string) error {
    cmd := exec.Command("aws", "ssm", "start-session", "--target", instanceId)
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout
    cmd.Stdin = os.Stdin
    
    // 设置子进程的进程组ID,使其独立
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Setpgid: true,
    }
    
    // 启动命令
    if err := cmd.Start(); err != nil {
        return err
    }
    
    // 创建信号通道
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
    
    // 等待命令完成或接收信号
    done := make(chan error, 1)
    go func() {
        done <- cmd.Wait()
    }()
    
    select {
    case err := <-done:
        return err
    case sig := <-sigCh:
        // 将信号传递给子进程
        if cmd.Process != nil {
            // 向整个进程组发送信号
            syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))
        }
        // 等待子进程退出
        return cmd.Wait()
    }
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: program <instance-id>")
        return
    }
    
    instanceId := os.Args[1]
    if err := commandRun(instanceId); err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

这个解决方案的关键点:

  1. 使用cmd.Start()cmd.Wait()而不是cmd.Run(),这样可以更好地控制进程
  2. 设置Setpgid: true创建独立的进程组
  3. 使用signal.Notify捕获CTRL-C信号
  4. 当收到信号时,使用syscall.Kill(-cmd.Process.Pid, sig.(syscall.Signal))将信号传递给整个进程组

这样当你在交互式shell中按CTRL-C时,信号会传递给AWS SSM会话中的子进程(如top),而不会终止Go包装程序。只有当AWS SSM会话本身退出时,Go程序才会结束。

回到顶部