gf/os/gproc/gproc_shell.go

151 lines
3.8 KiB
Go

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gproc
import (
"bytes"
"context"
"fmt"
"io"
"runtime"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
// Shell executes command `cmd` synchronously with given input pipe `in` and output pipe `out`.
// The command `cmd` reads the input parameters from input pipe `in`, and writes its output automatically
// to output pipe `out`.
func Shell(ctx context.Context, cmd string, out io.Writer, in io.Reader) error {
p := NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
)
p.Stdin = in
p.Stdout = out
return p.Run(ctx)
}
// ShellRun executes given command `cmd` synchronously and outputs the command result to the stdout.
func ShellRun(ctx context.Context, cmd string) error {
p := NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
)
return p.Run(ctx)
}
// ShellExec executes given command `cmd` synchronously and returns the command result.
func ShellExec(ctx context.Context, cmd string, environment ...[]string) (result string, err error) {
var (
buf = bytes.NewBuffer(nil)
p = NewProcess(
getShell(),
append([]string{getShellOption()}, parseCommand(cmd)...),
environment...,
)
)
p.Stdout = buf
p.Stderr = buf
err = p.Run(ctx)
result = buf.String()
return
}
// parseCommand parses command `cmd` into slice arguments.
//
// Note that it just parses the `cmd` for "cmd.exe" binary in windows, but it is not necessary
// parsing the `cmd` for other systems using "bash"/"sh" binary.
func parseCommand(cmd string) (args []string) {
if runtime.GOOS != "windows" {
return []string{cmd}
}
// Just for "cmd.exe" in windows.
var argStr string
var firstChar, prevChar, lastChar1, lastChar2 byte
array := gstr.SplitAndTrim(cmd, " ")
for _, v := range array {
if len(argStr) > 0 {
argStr += " "
}
firstChar = v[0]
lastChar1 = v[len(v)-1]
lastChar2 = 0
if len(v) > 1 {
lastChar2 = v[len(v)-2]
}
if prevChar == 0 && (firstChar == '"' || firstChar == '\'') {
// It should remove the first quote char.
argStr += v[1:]
prevChar = firstChar
} else if prevChar != 0 && lastChar2 != '\\' && lastChar1 == prevChar {
// It should remove the last quote char.
argStr += v[:len(v)-1]
args = append(args, argStr)
argStr = ""
prevChar = 0
} else if len(argStr) > 0 {
argStr += v
} else {
args = append(args, v)
}
}
return
}
// getShell returns the shell command depending on current working operating system.
// It returns "cmd.exe" for windows, and "bash" or "sh" for others.
func getShell() string {
switch runtime.GOOS {
case "windows":
return SearchBinary("cmd.exe")
default:
// Check the default binary storage path.
if gfile.Exists("/bin/bash") {
return "/bin/bash"
}
if gfile.Exists("/bin/sh") {
return "/bin/sh"
}
// Else search the env PATH.
path := SearchBinary("bash")
if path == "" {
path = SearchBinary("sh")
}
return path
}
}
// getShellOption returns the shell option depending on current working operating system.
// It returns "/c" for windows, and "-c" for others.
func getShellOption() string {
switch runtime.GOOS {
case "windows":
return "/c"
default:
return "-c"
}
}
// tracingEnvFromCtx converts OpenTelemetry propagation data as environment variables.
func tracingEnvFromCtx(ctx context.Context) []string {
var (
a = make([]string, 0)
m = make(map[string]string)
)
otel.GetTextMapPropagator().Inject(ctx, propagation.MapCarrier(m))
for k, v := range m {
a = append(a, fmt.Sprintf(`%s=%s`, k, v))
}
return a
}