package shell import ( "path/filepath" "strings" "syscall" ) type Executor struct { cwd string // 当前目录 pg *processGroup // --进程组 } func NewExecutor() *Executor { return &Executor{cwd: "/"} } func (e *Executor) Exec(p ExecuteParams) (*ExecuteResult, error) { if isCD(p.Cmd) { dir, err := resolveCD(e.cwd, p.Cmd) if err != nil { return &ExecuteResult{ Stderr: err.Error() + "\n", ExitCode: 1, }, nil } e.cwd = dir return &ExecuteResult{ExitCode: 0}, nil } if strings.TrimSpace(p.Cmd) == "pwd" { return &ExecuteResult{ Stdout: e.cwd + "\n", ExitCode: 0, }, nil } p.Dir = e.cwd defer func() { e.pg = nil // 命令结束 }() result, err := executeInternal(p, func(pg *processGroup) { e.pg = pg }) if result != nil { result.Cwd = e.cwd } return result, err } func isCD(cmd string) bool { s := strings.TrimSpace(cmd) return s == "cd" || strings.HasPrefix(s, "cd ") } func resolveCD(cwd, cmd string) (string, error) { fields := strings.Fields(cmd) if len(fields) == 1 { // 只有"cd", 没有参数 return "/", nil } path := fields[1] if filepath.IsAbs(path) { // 绝对路径 return filepath.Clean(path), nil } return filepath.Clean(filepath.Join(cwd, path)), nil // 相对路径 } func (e *Executor) Interrupt() error { if e.pg == nil { return nil } return syscall.Kill(-e.pg.pgid, syscall.SIGINT) // 等价于 Ctrl+C }