Last active
December 13, 2025 11:10
-
-
Save gnuos/adf3d75aa6e5764cceafe2eb4a656d2a to your computer and use it in GitHub Desktop.
Run a command in background
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "bufio" | |
| "context" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net" | |
| "net/rpc" | |
| "os" | |
| "os/exec" | |
| "os/signal" | |
| "sync" | |
| "syscall" | |
| "time" | |
| ) | |
| type Proc struct { | |
| cmd *exec.Cmd | |
| wg sync.WaitGroup | |
| ctx context.Context | |
| cancel context.CancelFunc | |
| out io.ReadCloser | |
| err io.ReadCloser | |
| } | |
| func newProc() *Proc { | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| cmd := exec.CommandContext(ctx, os.Args[1], os.Args[2:]...) | |
| stdout, err := cmd.StdoutPipe() | |
| if err != nil { | |
| log.Fatalf("创建 stdout pipe 失败: %v", err) | |
| } | |
| stderr, err := cmd.StderrPipe() | |
| if err != nil { | |
| log.Fatalf("创建 stderr pipe 失败: %v", err) | |
| } | |
| cmd.SysProcAttr = &syscall.SysProcAttr{ | |
| Setpgid: true, // 这个参数让Go通知子进程调用 setpgid(0, 0) | |
| } | |
| p := &Proc{ | |
| cmd: cmd, | |
| ctx: ctx, | |
| cancel: cancel, | |
| out: stdout, | |
| err: stderr, | |
| } | |
| return p | |
| } | |
| func run(p *Proc) { | |
| if err := p.cmd.Start(); err != nil { | |
| log.Fatalf("启动外部命令失败: %v", err) | |
| } | |
| log.Printf("子进程已启动,PID=%d", p.cmd.Process.Pid) | |
| go func() { | |
| timer := time.NewTimer(3 * time.Second) | |
| <-timer.C | |
| log.Println("检测:子进程已存活超过 3 秒") | |
| }() | |
| go func() { | |
| // 7. 等待子进程结束 | |
| if err := p.cmd.Wait(); err != nil { | |
| log.Printf("子进程退出: %v", err) | |
| } | |
| }() | |
| } | |
| func stop(p *Proc) error { | |
| pid := p.cmd.Process.Pid | |
| log.Printf("发送信号 %v 给子进程: %d", syscall.SIGINT, pid) | |
| var err error | |
| if p.cancel != nil { | |
| p.cancel() | |
| // 8. 等待 stdout/stderr 读取完毕 | |
| p.wg.Wait() | |
| } | |
| err = syscall.Kill(-pid, syscall.SIGINT) | |
| if err != nil { | |
| log.Println(err) | |
| } | |
| return err | |
| } | |
| func main() { | |
| if len(os.Args) < 2 { | |
| fmt.Fprintf(os.Stderr, "用法: %s <外部命令> [参数...]\n", os.Args[0]) | |
| os.Exit(1) | |
| } | |
| proc := newProc() | |
| // 3. 启动后台读取 goroutine,实时打印捕获到的输出 | |
| proc.wg.Add(2) | |
| go capture(&proc.wg, proc.out, "STDOUT") | |
| go capture(&proc.wg, proc.err, "STDERR") | |
| run(proc) | |
| server := rpc.NewServer() | |
| go startServer(server, proc) | |
| serverCh := make(chan os.Signal, 1) | |
| processCh := make(chan os.Signal, 1) | |
| signal.Notify(serverCh, os.Interrupt, syscall.SIGTERM) | |
| signal.Notify(processCh, syscall.SIGUSR1) | |
| done := make(chan struct{}, 1) | |
| go func() { | |
| sig := <-processCh | |
| log.Printf("收到信号 %v,准备退出...", sig) | |
| _ = stop(proc) | |
| done <- struct{}{} | |
| }() | |
| <-serverCh | |
| close(serverCh) | |
| processCh <- syscall.SIGUSR1 | |
| close(processCh) | |
| <-done | |
| log.Println("主进程退出") | |
| } | |
| type Monit struct { | |
| proc *Proc | |
| } | |
| func (ms *Monit) Stop(_ int, _ *[]byte) error { | |
| return stop(ms.proc) | |
| } | |
| func startServer(server *rpc.Server, p *Proc) { | |
| err := server.RegisterName("MonitService", &Monit{proc: p}) | |
| if err != nil { | |
| log.Println(err) | |
| } | |
| listener, err := net.Listen("tcp", ":1234") | |
| if err != nil { | |
| log.Fatal("ListenTCP error:", err) | |
| } | |
| conn, err := listener.Accept() | |
| if err != nil { | |
| log.Fatal("Accept error:", err) | |
| } | |
| rpc.ServeConn(conn) | |
| } | |
| // capture 把 reader 里的内容实时打印到控制台,并打上标签 | |
| func capture(wg *sync.WaitGroup, r io.Reader, label string) { | |
| defer wg.Done() | |
| scanner := bufio.NewScanner(r) | |
| for scanner.Scan() { | |
| fmt.Printf("[%s] %s\n", label, scanner.Text()) | |
| } | |
| if err := scanner.Err(); err != nil { | |
| if !errors.Is(err, os.ErrClosed) { | |
| log.Printf("[%s] 读取失败: %v", label, err) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment