dexec

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2023 License: Apache-2.0 Imports: 12 Imported by: 26

Documentation

Overview

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/dlib/dlog.WithLogger.

A Cmd logs when it starts, its exit status, and everything read from or written to .Stdin, .Stdout, and .Stderr if they aren't an *os.File. 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 (assuming the default dlog configuration):

time="2021-05-18T17:18:35-06:00" level=info dexec.pid=24272 msg="started command [\"printf\" \"%s\\n\" \"foo bar\" \"baz\"]"
time="2021-05-18T17:18:35-06:00" level=info dexec.pid=24272 dexec.stream=stdin msg="not logging input read from file \"/dev/stdin\""
time="2021-05-18T17:18:35-06:00" level=info dexec.pid=24272 dexec.stream=stdout+stderr dexec.data="foo bar\n"
time="2021-05-18T17:18:35-06:00" level=info dexec.pid=24272 dexec.stream=stdout+stderr dexec.data="baz\n"
time="2021-05-18T17:18:35-06:00" level=info dexec.pid=24272 msg="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

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotFound = exec.ErrNotFound

ErrNotFound is the os/exec.ErrNotFound value.

View Source
var LookPath = exec.LookPath

LookPath is the os/exe.LookPath function.

Functions

This section is empty.

Types

type Cmd

type Cmd struct {
	*exec.Cmd

	DisableLogging bool
	// contains filtered or unexported fields
}

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

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

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

The provided context is used for two purposes:

  1. To kill the process (by calling os.Process.Kill) if the context becomes done before the command completes on its own.
  2. For logging (see github.com/datawire/dlib/dlog).

See the os/exec.Command and os/exec.CommandContext documentation for more information.

Example
package main

import (
	"bytes"
	"context"
	"fmt"

	exec "github.com/datawire/dlib/dexec"
	"log"
	"strings"
)

func main() { // MODIFIED: FROM: func ExampleCommand() {
	cmd := exec.CommandContext(context.Background(), "tr", "a-z", "A-Z") // MODIFIED: FROM: cmd := exec.Command("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"

	exec "github.com/datawire/dlib/dexec"
	"log"
	"os"
)

func main() { // MODIFIED: FROM: func ExampleCommand_environment() {
	cmd := exec.CommandContext(context.Background(), "prog") // MODIFIED: FROM: cmd := exec.Command("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"

	exec "github.com/datawire/dlib/dexec"
	"time"
)

func main() { // MODIFIED: FROM: func ExampleCommandContext() {
	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

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

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

Example
package main

import (
	"context"
	"fmt"

	exec "github.com/datawire/dlib/dexec"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr") // MODIFIED: FROM: cmd := exec.Command("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

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.

Example
package main

import (
	"context"
	"fmt"

	exec "github.com/datawire/dlib/dexec"
	"log"
)

func main() {
	out, err := exec.CommandContext(context.Background(), "date").Output() // MODIFIED: FROM: out, err := exec.Command("date").Output()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("The date is %s\n", out)
}
Output:

func (*Cmd) Run

func (c *Cmd) Run() error

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"

	exec "github.com/datawire/dlib/dexec"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sleep", "1") // MODIFIED: FROM: cmd := exec.Command("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

func (c *Cmd) Start() error

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

See the os/exec.Cmd.Start documenaton for more information.

BUG(lukeshu) On GOOS=windows, it is an error to use a dcontext soft Context without also setting Cmd.SysProcAttr.CreationFlags |= syscall.CREATE_NEW_PROCESS_GROUP. This is because on Windows it is not possible to send the appropriate signal for graceful shutdown to just one process, it must be sent to the entire process group, which would involve sending it to ourselves. You must make the appropriate decision for your application whether to disable soft cancellation or whether to put the child process in its own process group.

Example
package main

import (
	"context"

	exec "github.com/datawire/dlib/dexec"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sleep", "5") // MODIFIED: FROM: cmd := exec.Command("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"

	exec "github.com/datawire/dlib/dexec"
	"io/ioutil"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "sh", "-c", "echo stdout; echo 1>&2 stderr") // MODIFIED: FROM: cmd := exec.Command("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"

	exec "github.com/datawire/dlib/dexec"
	"io"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "cat") // MODIFIED: FROM: cmd := exec.Command("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"

	exec "github.com/datawire/dlib/dexec"
	"log"
)

func main() {
	cmd := exec.CommandContext(context.Background(), "echo", "-n", `{"Name": "Bob", "Age": 32}`) // MODIFIED: FROM: cmd := exec.Command("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:

func (*Cmd) Wait

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.

See the os/exec.Cmd.Wait documenaton for more information.

type Error

type Error = exec.Error

Error is returned by LookPath when it fails to classify a file as an executable.

type ExitError

type ExitError = exec.ExitError

An ExitError reports an unsuccessful exit by a command.

Notes

Bugs

  • On GOOS=windows, it is an error to use a dcontext soft Context without also setting Cmd.SysProcAttr.CreationFlags |= syscall.CREATE_NEW_PROCESS_GROUP. This is because on Windows it is not possible to send the appropriate signal for graceful shutdown to just one process, it must be sent to the entire process group, which would involve sending it to ourselves. You must make the appropriate decision for your application whether to disable soft cancellation or whether to put the child process in its own process group.

Directories

Path Synopsis
internal
cfg
Package cfg holds configuration shared by the Go command and internal/testenv.
Package cfg holds configuration shared by the Go command and internal/testenv.
testenv
Package testenv provides information about what functionality is available in different testing environments run by the Go team.
Package testenv provides information about what functionality is available in different testing environments run by the Go team.

Jump to

Keyboard shortcuts

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