exex

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2021 License: MIT Imports: 5 Imported by: 0

README

exec - Capture stderr in exec.Cmd.Run

exex provides a custom Cmd type that wraps exec.Cmd in a way that it will always capture standard error stream if execution fails with an exec.ExitError.

The standard library exec package contains a very useful API to execute commands, however, the exec.Cmd.Run and exec.Cmd.Output methods behave differently in the case of a failed execution. In particular, exec.Cmd.Run will NOT populate exec.ExitError.Stderr in the case of failure, whereas exec.Cmd.Output will do. While this is explicitly noted in the exec package documentation, it is a source of confusion even for experienced users.

Another issue with the standard library package is that if we use exec.Cmd.Output to only capture the error we will be incurring in unnecessary allocations because it uses a bytes.Buffer for capturing STDOUT, and also STDERR will be truncated. The wrappers defined in this library do not have this peformance penalization nor truncate any output.

In order to avoid importing both this package and os/exec, this package provides aliases for the variables and top-level functions os/exec provides.

Benchmarks

$ go test -benchmem -bench=. -benchtime=1000x
goos: darwin
goarch: amd64
pkg: github.com/inkel/exex
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkCaptureStderr/stdlib-12         	    1000	   3263849 ns/op	   35560 B/op	      49 allocs/op
BenchmarkCaptureStderr/exex-12           	    1000	   3216620 ns/op	    2848 B/op	      39 allocs/op
BenchmarkRun-12                          	    1000	   3532978 ns/op	    2904 B/op	      39 allocs/op
BenchmarkRunContext-12                   	    1000	   3405547 ns/op	    3026 B/op	      41 allocs/op
BenchmarkRunCommand-12                   	    1000	   3588594 ns/op	    2904 B/op	      39 allocs/op
PASS
ok  	github.com/inkel/exex	17.660s

As you can see, the number of bytes and allocations per operation is drastically improved in exex.

A better comparison of the difference in performance can be done using benchstat:

# Generate benchmarks for stdlib
$ BENCHMARK=stdlib go test -benchmem -bench=CaptureStderr -benchtime=500x -count=10 > stdlib.txt

# Generate benchmarks for exex
$ BENCHMARK=exex go test -benchmem -bench=CaptureStderr -benchtime=500x -count=10 > exex.txt

# Compare the results
$ benchstat stdlib.txt exex.txt
name              old time/op    new time/op    delta
CaptureStderr-12    3.10ms ± 2%    3.11ms ± 2%     ~     (p=0.222 n=9+9)

name              old alloc/op   new alloc/op   delta
CaptureStderr-12    35.5kB ± 0%     2.9kB ± 2%  -91.91%  (p=0.000 n=9+10)

name              old allocs/op  new allocs/op  delta
CaptureStderr-12      49.0 ± 0%      39.0 ± 0%  -20.41%  (p=0.000 n=10+10)

If an image is worth a thousand words, how much worth is a benchmark?

License

See LICENSE, but basically, MIT.

Documentation

Overview

exex provides a custom Cmd type that wraps exec.Cmd in a way that it will always capture standard error stream if execution fails with an exec.ExitError.

The standard library exec package contains a very useful API to execute commands, however, the exec.Cmd.Run and exec.Cmd.Output methods behave differently in the case of a failed execution. In particular, exec.Cmd.Run will NOT populate exec.ExitError.Stderr in the case of failure, whereas exec.Cmd.Output will do. While this is explicitly noted in the exec package documentation, it is a source of confusion even for experienced users.

Another issue with the standard library package is that if we use exec.Cmd.Output to only capture the error we will be incurring in unnecessary allocations because it uses a bytes.Buffer for capturing STDOUT, and also STDERR will be truncated. The wrappers defined in this library do not have this peformance penalization nor truncate any output.

In order to avoid importing both this package and os/exec, this package provides aliases for the variables and top-level functions os/exec provides.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotFound = exec.ErrNotFound

ErrNotFound is an alias for exec.ErrNotFound, the error resulting if a path search failed to find an executable file.

View Source
var LookPath = exec.LookPath

LookPath is an alias for exec.LookPath, searches for an executable named file in the directories named by the PATH environment variable. Refer to that package for additional information.

Functions

func Run

func Run(cmd string, args ...string) error

Run creates a Cmd and returns the result of executing *Cmd.Run.

Example
package main

import (
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	err := exex.Run("sh", "-c", "foo")

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

func RunCommand

func RunCommand(cmd *exec.Cmd) error

RunCommand wraps an *exec.Cmd into a Cmd and returns the result of calling *Cmd.Run.

Example
package main

import (
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	cmd := exec.Command("sh", "-c", "foo")
	err := exex.RunCommand(cmd)

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

func RunContext

func RunContext(ctx context.Context, cmd string, args ...string) error

RunContext creates a Cmd with the given context and returns the result of executing *Cmd.Run.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	ctx := context.Background()
	err := exex.RunContext(ctx, "sh", "-c", "foo")

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

Types

type Cmd added in v0.0.2

type Cmd exec.Cmd

Cmd wraps exec.Cmd and represents an external command.

As in the case of exec.Cmd, a Cmd cannot be reused after executed for the first time.

Refer to the exec.Cmd documentation for information on all the functions this type provides except for Run, which is overwritten by this struct.

func Command added in v0.0.2

func Command(name string, args ...string) *Cmd

Command returns the Cmd struct to execute the named program with the given arguments.

Refer to the exec.Command documentation for additional information.

Example
package main

import (
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	cmd := exex.Command("sh", "-c", "foo")
	err := cmd.Run()

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

func CommandContext added in v0.0.2

func CommandContext(ctx context.Context, name string, args ...string) *Cmd

CommandContext is like Command but the Cmd is associated with a context.

Refer to the exec.Command documentation for additional information.

Example
package main

import (
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	cmd := exex.Command("sh", "-c", "foo")
	err := cmd.Run()

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

func (*Cmd) CombinedOutput added in v0.0.4

func (c *Cmd) CombinedOutput() ([]byte, error)

CombinedOutput runs the command and returns its combined standard output and standard error.

func (*Cmd) Output added in v0.0.4

func (c *Cmd) Output() ([]byte, error)

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 (*Cmd) Run added in v0.0.2

func (c *Cmd) Run() error

Run starts the command and waits for it to end.

If the command executes successfully (e.g. exits with a zero status), it returns nil.

If the command fails to execute, the error will be of type *exec.ExitError and it's always guaranteed that its Stderr property will have the contexts of the standard error stream, unless *Cmd.Stderr is specified.

Refer to exec.Cmd.Run documentation for additional information.

Example
package main

import (
	"errors"
	"fmt"
	"os/exec"

	"github.com/inkel/exex"
)

func main() {
	err := exex.Command("sh", "-c", "foo").Run()

	var exErr *exec.ExitError
	if errors.As(err, &exErr) {
		fmt.Printf("Captured stderr: %q\n", exErr.Stderr)
	} else {
		fmt.Printf("Expecting an *exec.ExitError, got %T: %[1]v\n", err)
	}
}
Output:

func (*Cmd) Start added in v0.0.4

func (c *Cmd) Start() error

Start starts the specified command but does not wait for it to complete.

func (*Cmd) StderrPipe added in v0.0.4

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.

func (*Cmd) StdinPipe added in v0.0.4

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.

func (*Cmd) StdoutPipe added in v0.0.4

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.

func (*Cmd) String added in v0.0.4

func (c *Cmd) String() string

String returns a human-readable description of c

func (*Cmd) Wait added in v0.0.4

func (c *Cmd) Wait() error

Wait waits for the command to exit and waits for any copying to stdin or copying from stdout or stderr to complete.

type Error added in v0.0.4

type Error = exec.Error

Error is a type alias for exec.Error

type ExitError added in v0.0.4

type ExitError = exec.ExitError

ExitError is a type alias for exec.ExitError

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL