github.com/gogf/gf@v1.16.9/os/gproc/gproc.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  // Package gproc implements management and communication for processes.
     8  package gproc
     9  
    10  import (
    11  	"bytes"
    12  	"github.com/gogf/gf/os/genv"
    13  	"github.com/gogf/gf/text/gstr"
    14  	"io"
    15  	"os"
    16  	"runtime"
    17  	"time"
    18  
    19  	"github.com/gogf/gf/os/gfile"
    20  	"github.com/gogf/gf/util/gconv"
    21  )
    22  
    23  const (
    24  	envKeyPPid = "GPROC_PPID"
    25  )
    26  
    27  var (
    28  	processPid       = os.Getpid() // processPid is the pid of current process.
    29  	processStartTime = time.Now()  // processStartTime is the start time of current process.
    30  )
    31  
    32  // Pid returns the pid of current process.
    33  func Pid() int {
    34  	return processPid
    35  }
    36  
    37  // PPid returns the custom parent pid if exists, or else it returns the system parent pid.
    38  func PPid() int {
    39  	if !IsChild() {
    40  		return Pid()
    41  	}
    42  	ppidValue := os.Getenv(envKeyPPid)
    43  	if ppidValue != "" && ppidValue != "0" {
    44  		return gconv.Int(ppidValue)
    45  	}
    46  	return PPidOS()
    47  }
    48  
    49  // PPidOS returns the system parent pid of current process.
    50  // Note that the difference between PPidOS and PPid function is that the PPidOS returns
    51  // the system ppid, but the PPid functions may return the custom pid by gproc if the custom
    52  // ppid exists.
    53  func PPidOS() int {
    54  	return os.Getppid()
    55  }
    56  
    57  // IsChild checks and returns whether current process is a child process.
    58  // A child process is forked by another gproc process.
    59  func IsChild() bool {
    60  	ppidValue := os.Getenv(envKeyPPid)
    61  	return ppidValue != "" && ppidValue != "0"
    62  }
    63  
    64  // SetPPid sets custom parent pid for current process.
    65  func SetPPid(ppid int) error {
    66  	if ppid > 0 {
    67  		return os.Setenv(envKeyPPid, gconv.String(ppid))
    68  	} else {
    69  		return os.Unsetenv(envKeyPPid)
    70  	}
    71  }
    72  
    73  // StartTime returns the start time of current process.
    74  func StartTime() time.Time {
    75  	return processStartTime
    76  }
    77  
    78  // Uptime returns the duration which current process has been running
    79  func Uptime() time.Duration {
    80  	return time.Now().Sub(processStartTime)
    81  }
    82  
    83  // Shell executes command <cmd> synchronizingly with given input pipe <in> and output pipe <out>.
    84  // The command <cmd> reads the input parameters from input pipe <in>, and writes its output automatically
    85  // to output pipe <out>.
    86  func Shell(cmd string, out io.Writer, in io.Reader) error {
    87  	p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
    88  	p.Stdin = in
    89  	p.Stdout = out
    90  	return p.Run()
    91  }
    92  
    93  // ShellRun executes given command <cmd> synchronizingly and outputs the command result to the stdout.
    94  func ShellRun(cmd string) error {
    95  	p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...))
    96  	return p.Run()
    97  }
    98  
    99  // ShellExec executes given command <cmd> synchronizingly and returns the command result.
   100  func ShellExec(cmd string, environment ...[]string) (string, error) {
   101  	buf := bytes.NewBuffer(nil)
   102  	p := NewProcess(getShell(), append([]string{getShellOption()}, parseCommand(cmd)...), environment...)
   103  	p.Stdout = buf
   104  	p.Stderr = buf
   105  	err := p.Run()
   106  	return buf.String(), err
   107  }
   108  
   109  // parseCommand parses command <cmd> into slice arguments.
   110  //
   111  // Note that it just parses the <cmd> for "cmd.exe" binary in windows, but it is not necessary
   112  // parsing the <cmd> for other systems using "bash"/"sh" binary.
   113  func parseCommand(cmd string) (args []string) {
   114  	if runtime.GOOS != "windows" {
   115  		return []string{cmd}
   116  	}
   117  	// Just for "cmd.exe" in windows.
   118  	var argStr string
   119  	var firstChar, prevChar, lastChar1, lastChar2 byte
   120  	array := gstr.SplitAndTrim(cmd, " ")
   121  	for _, v := range array {
   122  		if len(argStr) > 0 {
   123  			argStr += " "
   124  		}
   125  		firstChar = v[0]
   126  		lastChar1 = v[len(v)-1]
   127  		lastChar2 = 0
   128  		if len(v) > 1 {
   129  			lastChar2 = v[len(v)-2]
   130  		}
   131  		if prevChar == 0 && (firstChar == '"' || firstChar == '\'') {
   132  			// It should remove the first quote char.
   133  			argStr += v[1:]
   134  			prevChar = firstChar
   135  		} else if prevChar != 0 && lastChar2 != '\\' && lastChar1 == prevChar {
   136  			// It should remove the last quote char.
   137  			argStr += v[:len(v)-1]
   138  			args = append(args, argStr)
   139  			argStr = ""
   140  			prevChar = 0
   141  		} else if len(argStr) > 0 {
   142  			argStr += v
   143  		} else {
   144  			args = append(args, v)
   145  		}
   146  	}
   147  	return
   148  }
   149  
   150  // getShell returns the shell command depending on current working operation system.
   151  // It returns "cmd.exe" for windows, and "bash" or "sh" for others.
   152  func getShell() string {
   153  	switch runtime.GOOS {
   154  	case "windows":
   155  		return SearchBinary("cmd.exe")
   156  	default:
   157  		// Check the default binary storage path.
   158  		if gfile.Exists("/bin/bash") {
   159  			return "/bin/bash"
   160  		}
   161  		if gfile.Exists("/bin/sh") {
   162  			return "/bin/sh"
   163  		}
   164  		// Else search the env PATH.
   165  		path := SearchBinary("bash")
   166  		if path == "" {
   167  			path = SearchBinary("sh")
   168  		}
   169  		return path
   170  	}
   171  }
   172  
   173  // getShellOption returns the shell option depending on current working operation system.
   174  // It returns "/c" for windows, and "-c" for others.
   175  func getShellOption() string {
   176  	switch runtime.GOOS {
   177  	case "windows":
   178  		return "/c"
   179  	default:
   180  		return "-c"
   181  	}
   182  }
   183  
   184  // SearchBinary searches the binary <file> in current working folder and PATH environment.
   185  func SearchBinary(file string) string {
   186  	// Check if it's absolute path of exists at current working directory.
   187  	if gfile.Exists(file) {
   188  		return file
   189  	}
   190  	return SearchBinaryPath(file)
   191  }
   192  
   193  // SearchBinaryPath searches the binary <file> in PATH environment.
   194  func SearchBinaryPath(file string) string {
   195  	array := ([]string)(nil)
   196  	switch runtime.GOOS {
   197  	case "windows":
   198  		envPath := genv.Get("PATH", genv.Get("Path"))
   199  		if gstr.Contains(envPath, ";") {
   200  			array = gstr.SplitAndTrim(envPath, ";")
   201  		} else if gstr.Contains(envPath, ":") {
   202  			array = gstr.SplitAndTrim(envPath, ":")
   203  		}
   204  		if gfile.Ext(file) != ".exe" {
   205  			file += ".exe"
   206  		}
   207  	default:
   208  		array = gstr.SplitAndTrim(genv.Get("PATH"), ":")
   209  	}
   210  	if len(array) > 0 {
   211  		path := ""
   212  		for _, v := range array {
   213  			path = v + gfile.Separator + file
   214  			if gfile.Exists(path) && gfile.IsFile(path) {
   215  				return path
   216  			}
   217  		}
   218  	}
   219  	return ""
   220  }