Documentation ¶
Overview ¶
Package command can be a better replacement of exec/command package, provide more safety and handy methods, chain style.
Safety ¶
The argument for '%s' will be always safely escaped.
The argument for %s and "%s" will be always safely escaped except $VAR and ${VAR}, thus you can use shell variables in side arguments.
Below is true:
reflect.DeepEqual( command.NewSh(`echo %s '%s'`, "logs: $HOME/$abc/logs", "logs: $HOME/$abc/logs").Args, []string{"sh", "-c", `echo logs\:\ $HOME/$abc/logs logs\:\ \$HOME/\$abc/logs `} )
The New and NewSh method will escape any invalid shell characters, to avoid Remote Code Execution (RCE) attack or any form of Shell Injection, the escape will be denoted by below 2 forms:
- %s or "%s": will escape everything, except for shell variables like $ABC, or ${ABC}, any other variables form not accepted.
- '%s': will escape everything, shell variables also be escaped.
The New([]string, args...) and NewSh(string, args...) method argments just like fmt.Printf, the first arg is formatString, rest is format arguments, but with one exception: they can only accept %s as format placeholder. If you want use like %v, you can manually invoke [String()] method of the argument to pass as string.
Handy ¶
The package provider chain style invoking, like below:
command.NewSh(`echo %s '%s'`, "logs: $HOME/$abc/logs", "logs: $HOME/$abc/logs") .Stdout(os.Stdout) .Stdin(os.Stdin) .Timeout(time.Second*10) .CombinedOutput()
There methods can be chained(in the middle):
- command.UseSudo
- command.AsUser
- command.Timeout
- command.Context
- command.Env
- command.Dir
- command.Stdin
- command.Stdout
- command.Stderr
- command.Shell
- command.OnExit
But below methods cannot be chained(finalize):
For more information please checkout the godoc.
Index ¶
- func ReplaceShellString(s string, token *shlex.Token) string
- type Command
- func (c *Command) AsUser(osuser string) *Command
- func (c *Command) CombinedOutput() ([]byte, error)
- func (c *Command) Context(ctx context.Context) *Command
- func (c *Command) Dir(dir string) *Command
- func (c *Command) Env(env []string) *Command
- func (c *Command) OnExit(f ...func(*Command)) *Command
- func (c *Command) OnStart(f ...func(*Command)) *Command
- func (c *Command) Output() ([]byte, error)
- func (c *Command) Run() error
- func (c *Command) Shell(shellName string) *Command
- func (c *Command) Stderr(f io.Writer) *Command
- func (c *Command) Stdin(f io.Reader) *Command
- func (c *Command) Stdout(f io.Writer) *Command
- func (c *Command) Timeout(timeout time.Duration) *Command
- func (c *Command) UseSudo() *Command
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Command ¶
type Command struct { *exec.Cmd // Pid is the pid of command after start Pid int // LastError is the last recorded error after chain LastError error // Ctx is the context of the command, can check Err() on OnExit to see if the context be canceled Ctx context.Context // Cancel the context of the command, command will be killed, and Ctx.Err() not nil Cancel context.CancelFunc // contains filtered or unexported fields }
Command is embedded exec.Cmd struct, with some more state to use.
func New ¶
New return a Command instance to execute the named program with the given arguments, cmdArgs will be safely escaped, to avoid Remote Code Execution (RCE) attack or any form of Shell Injection, the escape will be denoted by below 2 forms:
- %s or "%s": will escape everything, except for shell variables like $ABC, or ${ABC}, any other variables form not accepted.
- '%s': will escape everything, shell variables also be escaped.
Command returns the Cmd struct to execute the named program with the given arguments.
It sets only the Path and Args in the returned structure.
If name contains no path separators, Command uses LookPath to resolve name to a complete path if possible. Otherwise it uses name directly as Path.
The returned Cmd's Args field is constructed from the command name followed by the elements of arg, so arg should not include the command name itself. For example, Command("echo", "hello"). Args[0] is always name, not the possibly resolved Path.
On Windows, processes receive the whole command line as a single string and do their own parsing. Command combines and quotes Args into a command line string with an algorithm compatible with applications using CommandLineToArgvW (which is the most common way). Notable exceptions are msiexec.exe and cmd.exe (and thus, all batch files), which have a different unquoting algorithm. In these or other similar cases, you can do the quoting yourself and provide the full command line in SysProcAttr.CmdLine, leaving Args empty.
Example ¶
package main import ( "fmt" "github.com/futurist/better-command/command" ) func main() { out, err := command.New( []string{"bash", "-c", "printf '%s' | awk '{print $0}'"}, "$(dangerous command) and $PASSWORD", ).Output() // all the `$` will be escaped, so it's safe fmt.Println(string(out), err) }
Output: $(dangerous command) and $PASSWORD <nil>
func NewBash ¶ added in v1.0.3
NewBash just like New, but run []string{"bash", "-c", cmdString} by default
func NewSh ¶
NewSh just like New, but run []string{"sh", "-c", cmdString} by default
Example ¶
package main import ( "fmt" "github.com/futurist/better-command/command" ) func main() { out, err := command.NewSh( "printf '%s' | awk '{print $0}'", "$(dangerous command) and $PASSWORD", ).Output() // all the `$` will be escaped, so it's safe fmt.Println(string(out), err) }
Output: $(dangerous command) and $PASSWORD <nil>
func (*Command) CombinedOutput ¶
CombinedOutput runs the command and returns its combined standard output and standard error.
func (*Command) Context ¶
Context can set command context that can cause the command be killed when canceled.
The provided context is used to kill the process (by calling os.Process.Kill) if the context becomes done before the command completes on its own.
func (*Command) OnExit ¶
OnExit set functions to run when command just exit, here can check the Ctx.Err() etc.
func (*Command) OnStart ¶
OnStart set functions to run when command just started
Example ¶
package main import ( "fmt" "strings" "github.com/futurist/better-command/command" ) func main() { out, err := command.New( []string{"bash", "-c", `echo "%s" | awk '{print '"$Var"' $0}'`}, "$(dangerous command) and normal $Var", ).Env( []string{"Var=123"}, ).OnStart( func(c *command.Command) { fmt.Printf("%#v", c.Cmd.Args) }, ).Output() // all the `$` will be escaped, so it's safe fmt.Println("--"+strings.TrimSpace(string(out))+"--", err) }
Output: []string{"bash", "-c", "echo \\$\\(dangerous\\ command\\)\\ and\\ normal\\ $Var | awk '{print '$Var' $0}'"}--123$(dangerous command) and normal 123-- <nil>
func (*Command) Output ¶
Output runs the command and returns its standard output. Any returned error will usually be of type *ExitError. If c.Stderr was nil, Output populates ExitError.Stderr.
func (*Command) Run ¶
Run starts the specified command and waits for it to complete.
The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.
If the command starts but does not complete successfully, the error is of type *ExitError. Other error types may be returned for other situations.
If the calling goroutine has locked the operating system thread with runtime.LockOSThread and modified any inheritable OS-level thread state (for example, Linux or Plan 9 name spaces), the new process will inherit the caller's thread state.
func (*Command) Shell ¶
Shell set command shell to shellName instead of 'sh', it must accept '-c' as second arg