Documentation ¶
Overview ¶
nolint
Package dexec is a logging variant of os/exec.
dexec is *almost* a drop-in replacement for os/exec. Differences are:
- The "Command" function is missing, because a context is always required; use CommandContext.
- It is not valid to create a "Cmd" entirely by hand; you must create it using CommandContext. After it has been created, you may adjust the fields as you would with an os/exec.Cmd.
The logger used is configured in the context.Context passed to CommandContext by calling github.com/datawire/ambassador/pkg/dlog.WithLogger.
A Cmd logs when it starts, its exit status, and if they aren't an *os.File, logs everything read from or written to .Stdin, .Stdout, and .Stderr. If one of those is an *os.File (as it is following a call to .StdinPipe, .StdoutPipe, or .StderrPipe), then that stream won't be logged (but it will print a message at process-start noting that it isn't being logged).
For example:
ctx := dlog.WithLogger(context.Background(), myLogger) cmd := dexec.CommandContext(ctx, "printf", "%s\n", "foo bar", "baz") cmd.Stdin = os.Stdin err := cmd.Run()
will log the lines
[pid:24272] started command []string{"printf", "%s\n", "foo bar", "baz"} [pid:24272] stdin < not logging input read from file /dev/stdin [pid:24272] stdout+stderr > "foo bar\n" [pid:24272] stdout+stderr > "baz\n" [pid:24272] finished successfully: exit status 0
If you would like a "pipe" to be logged, use an io.Pipe instead of calling .StdinPipe, .StdoutPipe, or .StderrPipe.
See the os/exec documentation for more information.
Index ¶
- Variables
- type Cmd
- func (c *Cmd) CombinedOutput() ([]byte, error)
- func (c *Cmd) Output() ([]byte, error)
- func (c *Cmd) Run() error
- func (c *Cmd) Start() error
- func (c *Cmd) StderrPipe() (io.ReadCloser, error)
- func (c *Cmd) StdinPipe() (io.WriteCloser, error)
- func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
- func (c *Cmd) Wait() error
- type Error
- type ExitError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = exec.ErrNotFound
ErrNotFound is the os/exec.ErrNotFound value.
var LookPath = exec.LookPath
LookPath is the os/exe.LookPath function.
Functions ¶
This section is empty.
Types ¶
type Cmd ¶
Cmd represents an external command being prepared or run.
A Cmd cannot be reused after calling its Run, Output or CombinedOutput methods.
See the os/exec.Cmd documentation for information on the fields within it.
Unlike an os/exec.Cmd, you MUST NOT construct a Cmd by hand, it must be created with CommandContext.
func CommandContext ¶
CommandContext returns the Cmd struct to execute the named program with the given arguments.
The provided context is used for two purposes:
- To kill the process (by calling os.Process.Kill) if the context becomes done before the command completes on its own.
- For logging (see github.com/datawire/ambassador/pkg/dlog).
See the os/exec.Command and os/exec.CommandContext documentation for more information.
Example ¶
package main import ( "bytes" "context" "fmt" "log" "strings" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "tr", "a-z", "A-Z") cmd.Stdin = strings.NewReader("some input") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { log.Fatal(err) } fmt.Printf("in all caps: %q\n", out.String()) }
Output:
Example (Environment) ¶
package main import ( "context" "log" "os" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "prog") cmd.Env = append(os.Environ(), "FOO=duplicate_value", // ignored "FOO=actual_value", // this value is used ) if err := cmd.Run(); err != nil { log.Fatal(err) } }
Output:
Example (Timeout) ¶
package main import ( "context" "time" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil { // This will fail after 100 milliseconds. The 5 second sleep // will be interrupted. } }
Output:
func (*Cmd) CombinedOutput ¶
CombinedOutput runs the command and returns its combined standard output and standard error.
Example ¶
package main import ( "context" "fmt" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr") stdoutStderr, err := cmd.CombinedOutput() if err != nil { log.Fatal(err) } fmt.Printf("%s\n", stdoutStderr) }
Output:
func (*Cmd) 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.
Example ¶
package main import ( "context" "fmt" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { out, err := exec.CommandContext(context.Background(), "date").Output() if err != nil { log.Fatal(err) } fmt.Printf("The date is %s\n", out) }
Output:
func (*Cmd) 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.
Example ¶
package main import ( "context" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "sleep", "1") log.Printf("Running command and waiting for it to finish...") err := cmd.Run() log.Printf("Command finished with error: %v", err) }
Output:
func (*Cmd) Start ¶
Start starts the specified command but does not wait for it to complete.
See the os/exec.Cmd.Start documenaton for more information.
Example ¶
package main import ( "context" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "sleep", "5") err := cmd.Start() if err != nil { log.Fatal(err) } log.Printf("Waiting for command to finish...") err = cmd.Wait() log.Printf("Command finished with error: %v", err) }
Output:
func (*Cmd) StderrPipe ¶
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe returns a pipe that will be connected to the command's standard error when the command starts.
This sets .Stderr to an *os.File, causing what you read from the pipe to not be logged.
See the os/exec.Cmd.StderrPipe documenaton for more information.
Example ¶
package main import ( "context" "fmt" "io/ioutil" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr") stderr, err := cmd.StderrPipe() if err != nil { log.Fatal(err) } if err := cmd.Start(); err != nil { log.Fatal(err) } slurp, _ := ioutil.ReadAll(stderr) fmt.Printf("%s\n", slurp) if err := cmd.Wait(); err != nil { log.Fatal(err) } }
Output:
func (*Cmd) StdinPipe ¶
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe returns a pipe that will be connected to the command's standard input when the command starts.
This sets .Stdin to an *os.File, causing what you write to the pipe to not be logged.
See the os/exec.Cmd.StdinPipe documenaton for more information.
Example ¶
package main import ( "context" "fmt" "io" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "cat") stdin, err := cmd.StdinPipe() if err != nil { log.Fatal(err) } go func() { defer stdin.Close() io.WriteString(stdin, "values written to stdin are passed to cmd's standard input") }() out, err := cmd.CombinedOutput() if err != nil { log.Fatal(err) } fmt.Printf("%s\n", out) }
Output:
func (*Cmd) StdoutPipe ¶
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe returns a pipe that will be connected to the command's standard output when the command starts.
This sets .Stdout to an *os.File, causing what you read from the pipe to not be logged.
See the os/exec.Cmd.StdoutPipe documenaton for more information.
Example ¶
package main import ( "context" "encoding/json" "fmt" "log" exec "github.com/datawire/ambassador/pkg/dexec" ) func main() { cmd := exec.CommandContext(context.Background(), "echo", "-n", `{"Name": "Bob", "Age": 32}`) stdout, err := cmd.StdoutPipe() if err != nil { log.Fatal(err) } if err := cmd.Start(); err != nil { log.Fatal(err) } var person struct { Name string Age int } if err := json.NewDecoder(stdout).Decode(&person); err != nil { log.Fatal(err) } if err := cmd.Wait(); err != nil { log.Fatal(err) } fmt.Printf("%s is %d years old\n", person.Name, person.Age) }
Output: