Skip to content

Instantly share code, notes, and snippets.

@gnuos
Last active December 13, 2025 11:10
Show Gist options
  • Select an option

  • Save gnuos/adf3d75aa6e5764cceafe2eb4a656d2a to your computer and use it in GitHub Desktop.

Select an option

Save gnuos/adf3d75aa6e5764cceafe2eb4a656d2a to your computer and use it in GitHub Desktop.
Run a command in background
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