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  }