gitee.com/h79/goutils@v1.22.10/common/system/cmd.go (about) 1 package system 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "time" 15 ) 16 17 type Config struct { 18 Stdin io.Reader 19 Stdout io.Writer 20 Stderr io.Writer 21 EnableStd bool `json:"enableStd" yaml:"enableStd" xml:"enableStd"` 22 EnableEnv bool `json:"enableEnv" yaml:"enableEnv" xml:"enableEnv"` 23 } 24 25 type Result struct { 26 stdout bytes.Buffer 27 stderr bytes.Buffer 28 Command string 29 Err error 30 StartTime time.Time 31 EndTime time.Time 32 } 33 34 func (r Result) Output() string { 35 if r.Err != nil { 36 return r.stderr.String() 37 } 38 return r.stdout.String() 39 } 40 41 func (r Result) Error() string { 42 if r.Err != nil { 43 return fmt.Sprintf("Result: %s, err: %v", r.Command, r.Err) 44 } 45 return fmt.Sprintf("Result: %s, Result: %s, StartTime:%v, EndTime: %v", r.Command, r.Output(), r.StartTime, r.EndTime) 46 } 47 48 func (c *Cmd) init(conf *Config, env ...string) *exec.Cmd { 49 if conf == nil { 50 return c.Cmd 51 } 52 if conf.EnableEnv { 53 c.Cmd.Env = os.Environ() 54 c.Cmd.Env = append(c.Cmd.Env, env...) 55 } 56 if conf.EnableStd { 57 c.Cmd.Stdin = os.Stdin 58 c.Cmd.Stdout = os.Stdout 59 c.Cmd.Stderr = os.Stderr 60 } else { 61 c.Cmd.Stdin = conf.Stdin 62 c.Cmd.Stdout = conf.Stdout 63 c.Cmd.Stderr = conf.Stderr 64 } 65 sysProcAttr(c.Cmd) 66 return c.Cmd 67 } 68 69 type Shell struct { 70 } 71 72 func (*Shell) init(args string) *exec.Cmd { 73 var args_ []string 74 var cmd *exec.Cmd 75 switch runtime.GOOS { 76 case "darwin": 77 fallthrough 78 case "linux": 79 args_ = append(args_, "-c") 80 args_ = append(args_, args) 81 cmd = exec.Command(os.Getenv("SHELL"), args_...) 82 break 83 case "windows": 84 args_ = append(args_, "/C") 85 args_ = append(args_, args) 86 cmd = exec.Command("cmd", args_...) 87 break 88 default: 89 os.Exit(1) 90 } 91 return cmd 92 } 93 94 func (s *Shell) Start(args []string, config Config, env ...string) error { 95 var c = NewCmd(s.init(strings.Join(args, " ")), &config, env...) 96 return c.Start() 97 } 98 99 // Run 阻塞式同步执行 100 func (s *Shell) Run(cmd string) Result { 101 res := Result{Command: cmd, StartTime: time.Now()} 102 conf := Config{Stdout: &res.stdout, Stderr: &res.stderr} 103 var c = NewCmd(s.init(cmd), &conf) 104 res.Err = c.Cmd.Run() 105 res.EndTime = time.Now() 106 return res 107 } 108 109 // RunTimeout 阻塞式超时同步执行 110 func (s *Shell) RunTimeout(cmd string, second time.Duration) Result { 111 res := Result{Command: cmd, StartTime: time.Now()} 112 conf := Config{Stdout: &res.stdout, Stderr: &res.stderr} 113 c := NewCmd(s.init(cmd), &conf) 114 tt := second * time.Second 115 if tt <= 0 { 116 tt = 30 * time.Second 117 } 118 t := time.NewTimer(tt) 119 defer t.Stop() 120 stop := make(chan struct{}, 1) 121 ChildRunning(func() { 122 res.Err = c.Cmd.Run() 123 res.EndTime = time.Now() 124 stop <- struct{}{} 125 }) 126 select { 127 case <-stop: 128 break 129 130 case <-t.C: 131 if c.Cmd.Process != nil { 132 err := c.Cmd.Process.Kill() 133 res.Err = fmt.Errorf("cmd time out, kill the process id= %d,%v", c.Cmd.Process.Pid, err) 134 return res 135 } 136 res.Err = fmt.Errorf("cmd time out") 137 return res 138 139 case <-Closed(): 140 break 141 } 142 return res 143 } 144 145 type Cmd struct { 146 Cmd *exec.Cmd 147 Err error 148 Data interface{} 149 asyncWait *RunningCheck 150 } 151 152 func NewArgCmd(id int, args []string, conf *Config, opts ...ArgOptionFunc) (*Cmd, error) { 153 var opt = ArgOption{Id: id, Args: args} 154 for i := range opts { 155 opts[i](&opt) 156 } 157 if len(opt.AppExe) == 0 { 158 opt.AppExe = opt.Args[0] 159 } 160 fileName, err := filepath.Abs(opt.AppExe) 161 if err != nil { 162 return nil, err 163 } 164 return NewCmd(exec.Command(fileName, opt.Args[1:]...), conf, opt.Env...).WithData(opt.Data), nil 165 } 166 167 func NewCmd(cmd *exec.Cmd, conf *Config, env ...string) *Cmd { 168 var c = &Cmd{Cmd: cmd} 169 c.init(conf, env...) 170 return c 171 } 172 173 func (c *Cmd) WithErr(err error) *Cmd { 174 c.Err = err 175 return c 176 } 177 178 func (c *Cmd) WithData(da interface{}) *Cmd { 179 c.Data = da 180 return c 181 } 182 183 func (c *Cmd) Start() error { 184 if c.Cmd == nil { 185 return errors.New("cmd not exist") 186 } 187 return c.Cmd.Start() 188 } 189 190 func (c *Cmd) Kill() error { 191 if c.Cmd == nil || c.Cmd.Process == nil { 192 return errors.New("cmd not exist") 193 } 194 return c.Cmd.Process.Kill() 195 } 196 197 func (c *Cmd) Wait() error { 198 if c == nil || c.Cmd == nil { 199 return errors.New("cmd not exist") 200 } 201 return c.Cmd.Wait() 202 } 203 204 // AsyncWait 异步 205 func (c *Cmd) AsyncWait(q func(cmd *Cmd, err error)) { 206 if c == nil || c.Cmd == nil { 207 q(nil, errors.New("cmd not exist")) 208 return 209 } 210 if c.asyncWait == nil { 211 c.asyncWait = &RunningCheck{} 212 } 213 c.asyncWait.GoRunning(func() { 214 err := c.Cmd.Wait() 215 q(c, err) 216 }) 217 } 218 219 func StripArgs(args []string, arg string) []string { 220 ll := len(args) 221 for i := 0; i < ll; { 222 if args[i] == arg { 223 next := 1 224 if i+1 < ll && args[i+1][0] != '-' { 225 next = 2 226 } 227 args = append(args[:i], args[i+next:]...) 228 break 229 } 230 i++ 231 } 232 return args 233 } 234 235 // SyncExec 超时同步执行 236 // timeout = 0, default 30 seconds 237 func SyncExec(cmd string, second time.Duration) Result { 238 var s = Shell{} 239 return s.RunTimeout(cmd, second) 240 } 241 242 func SyncCmd(ctx context.Context, command, env []string, dir string) (Result, error) { 243 res := Result{StartTime: time.Now()} 244 cmd := exec.CommandContext(ctx, command[0], command[1:]...) 245 cmd.Env = env 246 cmd.Dir = dir 247 out, err := cmd.CombinedOutput() 248 res.stdout.Write(out) 249 res.EndTime = time.Now() 250 if err != nil { 251 res.Err = err 252 return res, err 253 } 254 return res, nil 255 } 256 257 // AsyncExec 异步执行,返回chan 258 func AsyncExec(cmd string) <-chan Result { 259 resCh := make(chan Result, 1) 260 ChildRunning(func() { 261 var s = Shell{} 262 res := s.Run(cmd) 263 resCh <- res 264 }) 265 return resCh 266 } 267 268 // CreateShellFile 创建临时的 shell 脚本文件 269 // content 创建的脚本内容 270 func CreateShellFile(pattern, content string) (tmpFile string, err error) { 271 file, err := os.CreateTemp("", pattern) 272 if err != nil { 273 return 274 } 275 defer func() { 276 file.Close() 277 switch runtime.GOOS { 278 case "windows": 279 tmpFile = file.Name() + ".bat" 280 default: 281 tmpFile = file.Name() + ".sh" 282 } 283 err = os.Rename(file.Name(), tmpFile) 284 }() 285 286 err = file.Chmod(0777) 287 if err != nil { 288 return "", err 289 } 290 switch runtime.GOOS { 291 case "windows": 292 default: 293 _, err = file.WriteString("#!/bin/bash\n") 294 if err != nil { 295 return 296 } 297 _, err = file.WriteString("set -e\n") 298 if err != nil { 299 return 300 } 301 } 302 _, err = file.WriteString(content) 303 return 304 }