github.com/webx-top/com@v1.2.12/cmd.go (about)

     1  //go:build go1.2
     2  // +build go1.2
     3  
     4  // Copyright 2013 com authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     7  // not use this file except in compliance with the License. You may obtain
     8  // a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    14  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    15  // License for the specific language governing permissions and limitations
    16  // under the License.
    17  
    18  // Package com is an open source project for commonly used functions for the Go programming language.
    19  package com
    20  
    21  import (
    22  	"bufio"
    23  	"bytes"
    24  	"context"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"log"
    29  	"os"
    30  	"os/exec"
    31  	"regexp"
    32  	"runtime"
    33  	"strconv"
    34  	"strings"
    35  	"sync"
    36  	"time"
    37  )
    38  
    39  var ErrCmdNotRunning = errors.New(`command is not running`)
    40  
    41  // ElapsedMemory 内存占用
    42  func ElapsedMemory() (ret string) {
    43  	memStat := new(runtime.MemStats)
    44  	runtime.ReadMemStats(memStat)
    45  	ret = FormatByte(memStat.Alloc, 3)
    46  	return
    47  }
    48  
    49  // ExecCmdDirBytesWithContext executes system command in given directory
    50  // and return stdout, stderr in bytes type, along with possible error.
    51  func ExecCmdDirBytesWithContext(ctx context.Context, dir, cmdName string, args ...string) ([]byte, []byte, error) {
    52  	bufOut := new(bytes.Buffer)
    53  	bufErr := new(bytes.Buffer)
    54  
    55  	cmd := exec.CommandContext(ctx, cmdName, args...)
    56  	cmd.Dir = dir
    57  	cmd.Stdout = bufOut
    58  	cmd.Stderr = bufErr
    59  
    60  	err := cmd.Run()
    61  	if err != nil {
    62  		if e, y := err.(*exec.ExitError); y {
    63  			OnCmdExitError(append([]string{cmdName}, args...), e)
    64  		} else {
    65  			cmd.Stderr.Write([]byte(err.Error() + "\n"))
    66  		}
    67  	}
    68  	return bufOut.Bytes(), bufErr.Bytes(), err
    69  }
    70  
    71  // ExecCmdDirBytes executes system command in given directory
    72  // and return stdout, stderr in bytes type, along with possible error.
    73  func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) {
    74  	return ExecCmdDirBytesWithContext(context.Background(), dir, cmdName, args...)
    75  }
    76  
    77  // ExecCmdBytes executes system command
    78  // and return stdout, stderr in bytes type, along with possible error.
    79  func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) {
    80  	return ExecCmdBytesWithContext(context.Background(), cmdName, args...)
    81  }
    82  
    83  // ExecCmdBytesWithContext executes system command
    84  // and return stdout, stderr in bytes type, along with possible error.
    85  func ExecCmdBytesWithContext(ctx context.Context, cmdName string, args ...string) ([]byte, []byte, error) {
    86  	return ExecCmdDirBytesWithContext(ctx, "", cmdName, args...)
    87  }
    88  
    89  // ExecCmdDir executes system command in given directory
    90  // and return stdout, stderr in string type, along with possible error.
    91  func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) {
    92  	return ExecCmdDirWithContext(context.Background(), dir, cmdName, args...)
    93  }
    94  
    95  // ExecCmdDirWithContext executes system command in given directory
    96  // and return stdout, stderr in string type, along with possible error.
    97  func ExecCmdDirWithContext(ctx context.Context, dir, cmdName string, args ...string) (string, string, error) {
    98  	bufOut, bufErr, err := ExecCmdDirBytesWithContext(ctx, dir, cmdName, args...)
    99  	return string(bufOut), string(bufErr), err
   100  }
   101  
   102  // ExecCmd executes system command
   103  // and return stdout, stderr in string type, along with possible error.
   104  func ExecCmd(cmdName string, args ...string) (string, string, error) {
   105  	return ExecCmdWithContext(context.Background(), cmdName, args...)
   106  }
   107  
   108  // ExecCmdWithContext executes system command
   109  // and return stdout, stderr in string type, along with possible error.
   110  func ExecCmdWithContext(ctx context.Context, cmdName string, args ...string) (string, string, error) {
   111  	return ExecCmdDirWithContext(ctx, "", cmdName, args...)
   112  }
   113  
   114  // WritePidFile writes the process ID to the file at PidFile.
   115  // It does nothing if PidFile is not set.
   116  func WritePidFile(pidFile string, pidNumbers ...int) error {
   117  	if pidFile == "" {
   118  		return nil
   119  	}
   120  	var pidNumber int
   121  	if len(pidNumbers) > 0 {
   122  		pidNumber = pidNumbers[0]
   123  	} else {
   124  		pidNumber = os.Getpid()
   125  	}
   126  	pid := []byte(strconv.Itoa(pidNumber) + "\n")
   127  	return os.WriteFile(pidFile, pid, 0644)
   128  }
   129  
   130  var (
   131  	equal  = rune('=')
   132  	space  = rune(' ')
   133  	quote  = rune('"')
   134  	squote = rune('\'')
   135  	slash  = rune('\\')
   136  	tab    = rune('\t')
   137  	envOS  = regexp.MustCompile(`\{\$[a-zA-Z0-9_]+(:[^}]*)?\}`)
   138  	envWin = regexp.MustCompile(`\{%[a-zA-Z0-9_]+(:[^}]*)?%\}`)
   139  )
   140  
   141  func ParseFields(row string) (fields []string) {
   142  	item := []rune{}
   143  	hasQuote := false
   144  	hasSlash := false
   145  	hasSpace := false
   146  	var foundQuote rune
   147  	maxIndex := len(row) - 1
   148  	//drwxr-xr-x   1 root root    0 2023-11-19 04:18 'test test2'
   149  	for k, v := range row {
   150  		if !hasQuote {
   151  			if v == space || v == tab {
   152  				if hasSpace {
   153  					continue
   154  				}
   155  				hasSpace = true
   156  				fields = append(fields, string(item))
   157  				item = []rune{}
   158  				continue
   159  			}
   160  			if hasSpace {
   161  				if v == quote || v == squote {
   162  					hasSpace = false
   163  					hasQuote = true
   164  					foundQuote = v
   165  					continue
   166  				}
   167  			}
   168  			hasSpace = false
   169  		} else {
   170  			hasSpace = false
   171  			if !hasSlash && v == foundQuote {
   172  				hasQuote = false
   173  				continue
   174  			}
   175  			if !hasSlash && v == slash && k+1 <= maxIndex && rune(row[k+1]) == foundQuote {
   176  				hasSlash = true
   177  				continue
   178  			}
   179  			hasSlash = false
   180  		}
   181  		item = append(item, v)
   182  	}
   183  	if len(item) > 0 {
   184  		fields = append(fields, string(item))
   185  	}
   186  	return
   187  }
   188  
   189  func ParseArgs(command string, disableParseEnvVar ...bool) (params []string) {
   190  	item := []rune{}
   191  	hasQuote := false
   192  	hasSlash := false
   193  	hasSpace := false
   194  	var foundQuote rune
   195  	maxIndex := len(command) - 1
   196  	//tower.exe -c tower.yaml -p "eee\"ddd" -t aaaa
   197  	for k, v := range command {
   198  		if !hasQuote {
   199  			if v == space || v == tab {
   200  				if hasSpace {
   201  					continue
   202  				}
   203  				hasSpace = true
   204  				params = append(params, string(item))
   205  				item = []rune{}
   206  				continue
   207  			}
   208  			hasSpace = false
   209  			if v == equal {
   210  				params = append(params, string(item))
   211  				item = []rune{}
   212  				continue
   213  			}
   214  			if v == quote || v == squote {
   215  				hasQuote = true
   216  				foundQuote = v
   217  				continue
   218  			}
   219  		} else {
   220  			hasSpace = false
   221  			if !hasSlash && v == foundQuote {
   222  				hasQuote = false
   223  				continue
   224  			}
   225  			if !hasSlash && v == slash && k+1 <= maxIndex && rune(command[k+1]) == foundQuote {
   226  				hasSlash = true
   227  				continue
   228  			}
   229  			hasSlash = false
   230  		}
   231  		item = append(item, v)
   232  	}
   233  	if len(item) > 0 {
   234  		params = append(params, string(item))
   235  	}
   236  	if len(disableParseEnvVar) == 0 || !disableParseEnvVar[0] {
   237  		for k, v := range params {
   238  			v = ParseWindowsEnvVar(v)
   239  			params[k] = ParseEnvVar(v)
   240  		}
   241  	}
   242  	return
   243  }
   244  
   245  func ParseEnvVar(v string, cb ...func(string) string) string {
   246  	if len(cb) > 0 && cb[0] != nil {
   247  		return envOS.ReplaceAllStringFunc(v, cb[0])
   248  	}
   249  	return envOS.ReplaceAllStringFunc(v, getEnv)
   250  }
   251  
   252  func ParseWindowsEnvVar(v string, cb ...func(string) string) string {
   253  	if len(cb) > 0 && cb[0] != nil {
   254  		return envWin.ReplaceAllStringFunc(v, cb[0])
   255  	}
   256  	return envWin.ReplaceAllStringFunc(v, getWinEnv)
   257  }
   258  
   259  func GetWinEnvVarName(s string) string {
   260  	s = strings.TrimPrefix(s, `{%`)
   261  	s = strings.TrimSuffix(s, `%}`)
   262  	return s
   263  }
   264  
   265  func getWinEnv(s string) string {
   266  	s = GetWinEnvVarName(s)
   267  	return GetenvOr(s)
   268  }
   269  
   270  func GetEnvVarName(s string) string {
   271  	s = strings.TrimPrefix(s, `{$`)
   272  	s = strings.TrimSuffix(s, `}`)
   273  	return s
   274  }
   275  
   276  func getEnv(s string) string {
   277  	s = GetEnvVarName(s)
   278  	return GetenvOr(s)
   279  }
   280  
   281  type CmdResultCapturer struct {
   282  	Do func([]byte) error
   283  }
   284  
   285  func (c CmdResultCapturer) Write(p []byte) (n int, err error) {
   286  	err = c.Do(p)
   287  	n = len(p)
   288  	return
   289  }
   290  
   291  func (c CmdResultCapturer) WriteString(p string) (n int, err error) {
   292  	err = c.Do([]byte(p))
   293  	n = len(p)
   294  	return
   295  }
   296  
   297  func NewCmdChanReader() *CmdChanReader {
   298  	return &CmdChanReader{ch: make(chan io.Reader)}
   299  }
   300  
   301  type CmdChanReader struct {
   302  	ch chan io.Reader
   303  	mu sync.RWMutex
   304  }
   305  
   306  func (c *CmdChanReader) getCh() chan io.Reader {
   307  	c.mu.RLock()
   308  	ch := c.ch
   309  	c.mu.RUnlock()
   310  	return ch
   311  }
   312  
   313  func (c *CmdChanReader) setCh(ch chan io.Reader) {
   314  	c.mu.Lock()
   315  	c.ch = ch
   316  	c.mu.Unlock()
   317  }
   318  
   319  func (c *CmdChanReader) Read(p []byte) (n int, err error) {
   320  	ch := c.getCh()
   321  	if ch == nil {
   322  		ch = make(chan io.Reader)
   323  		c.setCh(ch)
   324  	}
   325  	r := <-ch
   326  	if r == nil {
   327  		return 0, errors.New(`[CmdChanReader] Chan has been closed`)
   328  	}
   329  	return r.Read(p)
   330  }
   331  
   332  func (c *CmdChanReader) Close() {
   333  	ch := c.getCh()
   334  	if ch == nil {
   335  		return
   336  	}
   337  	close(ch)
   338  	c.setCh(nil)
   339  }
   340  
   341  func (c *CmdChanReader) SendReader(r io.Reader) *CmdChanReader {
   342  	ch := c.getCh()
   343  	if ch == nil {
   344  		return c
   345  	}
   346  	defer recover()
   347  	select {
   348  	case ch <- r:
   349  	default:
   350  	}
   351  	return c
   352  }
   353  
   354  func (c *CmdChanReader) SendReaderAndWait(r io.Reader) *CmdChanReader {
   355  	ch := c.getCh()
   356  	if ch == nil {
   357  		return c
   358  	}
   359  	defer recover()
   360  	ch <- r
   361  	return c
   362  }
   363  
   364  func (c *CmdChanReader) Send(b []byte) *CmdChanReader {
   365  	return c.SendReader(bytes.NewReader(b))
   366  }
   367  
   368  func (c *CmdChanReader) SendString(s string) *CmdChanReader {
   369  	return c.SendReader(strings.NewReader(s))
   370  }
   371  
   372  func (c *CmdChanReader) SendAndWait(b []byte) *CmdChanReader {
   373  	return c.SendReaderAndWait(bytes.NewReader(b))
   374  }
   375  
   376  func (c *CmdChanReader) SendStringAndWait(s string) *CmdChanReader {
   377  	return c.SendReaderAndWait(strings.NewReader(s))
   378  }
   379  
   380  // WatchingStdin Watching os.Stdin
   381  // example: go WatchingStdin(ctx,`name`,fn)
   382  func WatchingStdin(ctx context.Context, name string, fn func(string) error) {
   383  	in := bufio.NewReader(os.Stdin)
   384  	for {
   385  		select {
   386  		case <-ctx.Done():
   387  			return
   388  		default:
   389  			input, err := in.ReadString(LF)
   390  			if err != nil && err != io.EOF {
   391  				log.Printf(`watchingStdin(%s): %s`+StrLF, name, err.Error())
   392  				return
   393  			}
   394  			err = fn(input)
   395  			if err != nil {
   396  				log.Printf(`watchingStdin(%s): %s`+StrLF, name, err.Error())
   397  				return
   398  			}
   399  		}
   400  	}
   401  }
   402  
   403  func NewCmdStartResultCapturer(writer io.Writer, duration time.Duration) *CmdStartResultCapturer {
   404  	return &CmdStartResultCapturer{
   405  		writer:   writer,
   406  		duration: duration,
   407  		started:  time.Now(),
   408  		buffer:   bytes.NewBuffer(nil),
   409  	}
   410  }
   411  
   412  type CmdStartResultCapturer struct {
   413  	writer   io.Writer
   414  	started  time.Time
   415  	duration time.Duration
   416  	buffer   *bytes.Buffer
   417  }
   418  
   419  func (c *CmdStartResultCapturer) Write(p []byte) (n int, err error) {
   420  	if time.Since(c.started) < c.duration {
   421  		c.buffer.Write(p)
   422  	}
   423  	return c.writer.Write(p)
   424  }
   425  
   426  func (c *CmdStartResultCapturer) Buffer() *bytes.Buffer {
   427  	return c.buffer
   428  }
   429  
   430  func (c *CmdStartResultCapturer) Writer() io.Writer {
   431  	return c.writer
   432  }
   433  
   434  func CreateCmdStr(command string, recvResult func([]byte) error) *exec.Cmd {
   435  	return CreateCmdStrWithContext(context.Background(), command, recvResult)
   436  }
   437  
   438  func CreateCmdStrWithContext(ctx context.Context, command string, recvResult func([]byte) error) *exec.Cmd {
   439  	out := CmdResultCapturer{Do: recvResult}
   440  	return CreateCmdStrWithWriter(command, out)
   441  }
   442  
   443  func CreateCmd(params []string, recvResult func([]byte) error) *exec.Cmd {
   444  	return CreateCmdWithContext(context.Background(), params, recvResult)
   445  }
   446  
   447  func CreateCmdWithContext(ctx context.Context, params []string, recvResult func([]byte) error) *exec.Cmd {
   448  	out := CmdResultCapturer{Do: recvResult}
   449  	return CreateCmdWriterWithContext(ctx, params, out)
   450  }
   451  
   452  func CreateCmdStrWithWriter(command string, writer ...io.Writer) *exec.Cmd {
   453  	return CreateCmdStrWriterWithContext(context.Background(), command, writer...)
   454  }
   455  
   456  func CreateCmdStrWriterWithContext(ctx context.Context, command string, writer ...io.Writer) *exec.Cmd {
   457  	params := ParseArgs(command)
   458  	return CreateCmdWriterWithContext(ctx, params, writer...)
   459  }
   460  
   461  func CreateCmdWithWriter(params []string, writer ...io.Writer) *exec.Cmd {
   462  	return CreateCmdWriterWithContext(context.Background(), params, writer...)
   463  }
   464  
   465  func CreateCmdWriterWithContext(ctx context.Context, params []string, writer ...io.Writer) *exec.Cmd {
   466  	var cmd *exec.Cmd
   467  	length := len(params)
   468  	if length == 0 || len(params[0]) == 0 {
   469  		return cmd
   470  	}
   471  	if length > 1 {
   472  		cmd = exec.CommandContext(ctx, params[0], params[1:]...)
   473  	} else {
   474  		cmd = exec.CommandContext(ctx, params[0])
   475  	}
   476  	var wOut, wErr io.Writer = os.Stdout, os.Stderr
   477  	length = len(writer)
   478  	if length > 0 {
   479  		if writer[0] != nil {
   480  			wOut = writer[0]
   481  		}
   482  		if length > 1 && writer[1] != nil {
   483  			wErr = writer[1]
   484  		}
   485  	}
   486  	cmd.Stdout = wOut
   487  	cmd.Stderr = wErr
   488  	return cmd
   489  }
   490  
   491  func RunCmdStr(command string, recvResult func([]byte) error) *exec.Cmd {
   492  	return RunCmdStrWithContext(context.Background(), command, recvResult)
   493  }
   494  
   495  func RunCmdStrWithContext(ctx context.Context, command string, recvResult func([]byte) error) *exec.Cmd {
   496  	out := CmdResultCapturer{Do: recvResult}
   497  	return RunCmdStrWriterWithContext(ctx, command, out)
   498  }
   499  
   500  func RunCmd(params []string, recvResult func([]byte) error) *exec.Cmd {
   501  	return RunCmdWithContext(context.Background(), params, recvResult)
   502  }
   503  
   504  func RunCmdWithContext(ctx context.Context, params []string, recvResult func([]byte) error) *exec.Cmd {
   505  	out := CmdResultCapturer{Do: recvResult}
   506  	return RunCmdWriterWithContext(ctx, params, out)
   507  }
   508  
   509  func RunCmdStrWithWriter(command string, writer ...io.Writer) *exec.Cmd {
   510  	return RunCmdStrWriterWithContext(context.Background(), command, writer...)
   511  }
   512  
   513  func RunCmdStrWriterWithContext(ctx context.Context, command string, writer ...io.Writer) *exec.Cmd {
   514  	params := ParseArgs(command)
   515  	return RunCmdWriterWithContext(ctx, params, writer...)
   516  }
   517  
   518  var OnCmdExitError = func(params []string, err *exec.ExitError) {
   519  	fmt.Printf("[%v]The process exited abnormally: PID(%d) PARAMS(%v) ERR(%v)\n", time.Now().Format(`2006-01-02 15:04:05`), err.Pid(), params, err)
   520  }
   521  
   522  func RunCmdReaderWriterWithContext(ctx context.Context, params []string, reader io.Reader, writer ...io.Writer) *exec.Cmd {
   523  	cmd := CreateCmdWriterWithContext(ctx, params, writer...)
   524  	cmd.Stdin = reader
   525  
   526  	go func() {
   527  		err := cmd.Run()
   528  		if err != nil {
   529  			if e, y := err.(*exec.ExitError); y {
   530  				OnCmdExitError(params, e)
   531  			} else {
   532  				cmd.Stderr.Write([]byte(err.Error() + "\n"))
   533  			}
   534  		}
   535  	}()
   536  
   537  	return cmd
   538  }
   539  
   540  func RunCmdWithReaderWriter(params []string, reader io.Reader, writer ...io.Writer) *exec.Cmd {
   541  	return RunCmdReaderWriterWithContext(context.Background(), params, reader, writer...)
   542  }
   543  
   544  func RunCmdWithWriter(params []string, writer ...io.Writer) *exec.Cmd {
   545  	return RunCmdWriterWithContext(context.Background(), params, writer...)
   546  }
   547  
   548  func RunCmdWriterWithContext(ctx context.Context, params []string, writer ...io.Writer) *exec.Cmd {
   549  	cmd := CreateCmdWriterWithContext(ctx, params, writer...)
   550  
   551  	go func() {
   552  		err := cmd.Run()
   553  		if err != nil {
   554  			if e, y := err.(*exec.ExitError); y {
   555  				OnCmdExitError(params, e)
   556  			} else {
   557  				cmd.Stderr.Write([]byte(err.Error() + "\n"))
   558  			}
   559  		}
   560  	}()
   561  
   562  	return cmd
   563  }
   564  
   565  func RunCmdWithWriterx(params []string, wait time.Duration, writer ...io.Writer) (cmd *exec.Cmd, err error, newOut *CmdStartResultCapturer, newErr *CmdStartResultCapturer) {
   566  	return RunCmdWriterxWithContext(context.Background(), params, wait, writer...)
   567  }
   568  
   569  func RunCmdWriterxWithContext(ctx context.Context, params []string, wait time.Duration, writer ...io.Writer) (cmd *exec.Cmd, err error, newOut *CmdStartResultCapturer, newErr *CmdStartResultCapturer) {
   570  	length := len(writer)
   571  	var wOut, wErr io.Writer = os.Stdout, os.Stderr
   572  	if length > 0 {
   573  		if writer[0] != nil {
   574  			wOut = writer[0]
   575  		}
   576  		if length > 1 {
   577  			if writer[1] != nil {
   578  				wErr = writer[1]
   579  			}
   580  		}
   581  	}
   582  	newOut = NewCmdStartResultCapturer(wOut, wait)
   583  	newErr = NewCmdStartResultCapturer(wErr, wait)
   584  	writer = []io.Writer{newOut, newErr}
   585  	cmd = CreateCmdWriterWithContext(ctx, params, writer...)
   586  	go func() {
   587  		err = cmd.Run()
   588  		if err != nil {
   589  			if e, y := err.(*exec.ExitError); y {
   590  				OnCmdExitError(params, e)
   591  			} else {
   592  				cmd.Stderr.Write([]byte(err.Error() + "\n"))
   593  			}
   594  		}
   595  	}()
   596  	time.Sleep(wait)
   597  	return
   598  }
   599  
   600  func CloseProcessFromPidFile(pidFile string) error {
   601  	if pidFile == `` {
   602  		return nil
   603  	}
   604  	b, err := os.ReadFile(pidFile)
   605  	if err != nil {
   606  		if os.IsNotExist(err) {
   607  			return nil
   608  		}
   609  		return err
   610  	}
   611  	pid, err := strconv.Atoi(strings.TrimSpace(string(b)))
   612  	if err != nil {
   613  		return nil
   614  	}
   615  	return CloseProcessFromPid(pid)
   616  }
   617  
   618  func CloseProcessFromPid(pid int) error {
   619  	if pid <= 0 {
   620  		return nil
   621  	}
   622  	procs, err := os.FindProcess(pid)
   623  	if err == nil {
   624  		err = procs.Kill()
   625  	}
   626  	if err != nil && errors.Is(err, os.ErrProcessDone) {
   627  		return nil
   628  	}
   629  	return err
   630  }
   631  
   632  func CloseProcessFromCmd(cmd *exec.Cmd) error {
   633  	if cmd == nil {
   634  		return nil
   635  	}
   636  	if cmd.Process == nil {
   637  		return nil
   638  	}
   639  	err := cmd.Process.Kill()
   640  	if err != nil && errors.Is(err, os.ErrProcessDone) {
   641  		return nil
   642  	}
   643  	if cmd.ProcessState == nil || cmd.ProcessState.Exited() {
   644  		return nil
   645  	}
   646  	return err
   647  }
   648  
   649  func CmdIsRunning(cmd *exec.Cmd) bool {
   650  	return cmd != nil && cmd.ProcessState == nil
   651  }