run

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 20, 2019 License: MIT Imports: 13 Imported by: 1

README

go-run

Run is a Go (golang) package that wraps the standard Go os/exec and golang.org/x/crypto/ssh packages to run commands either locally or over ssh while capturing stdout, stderr, and exit codes.

GoDoc

Features

  • Run commands either locally or remotely over SSH.
  • Run commands in a shell or directly ala glibc's exec().
  • Capture stdout, stderr, and exit code.
  • Output can be redirected to any Writer.

Documentation

Documentation can be found at GoDoc

Installation

Install wordwrap using the "go get" command:

$ go get github.com/apatters/go-run

The Go distribution is run's only dependency.

Examples

Local

Local is used to run commands on the local host.

package main

import (
	"fmt"

	"github.com/apatters/go-run"
)

func main() {
	// Initialize Local object using defaults.
	runner := run.NewLocal(run.LocalConfig{})

	fmt.Println("Run ls command.")
	stdout, stderr, code, _ := runner.Run(
		"/bin/ls",
		"-1",
		"/bin/true",
		"/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command with an expected error.")
	stdout, stderr, code, _ = runner.Run(
		"/bin/ls",
		"-1",
		"/bin/true",
		"/bin/false",
		"/xyzzy")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command after changing directory.")
	runner = run.NewLocal(run.LocalConfig{Dir: "/bin"})
	stdout, stderr, code, _ = runner.Run(
		"/bin/ls",
		"-1",
		"true",
		"false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command using shell.")
	runner = run.NewLocal(run.LocalConfig{})
	stdout, stderr, code, _ = runner.Shell("/bin/ls -1 /bin/true /bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command using shell with an expected error.")
	stdout, stderr, code, _ = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run complex shell command.")
	runner = run.NewLocal(run.LocalConfig{})
	stdout, stderr, code, _ = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()
}

Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n"
exit code = 2

Run ls command after changing directory.
stdout = "false\ntrue\n"
stderr = ""
exit code = 0

Run ls command using shell.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command using shell with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n"
exit code = 2

Run complex shell command.
stdout = "false\n"
stderr = ""
exit code = 0
Remote

Remote is used to run commands on remote hosts using SSH. It defaults to using the current user name and the user's public SSH key for authentication. Ssh-agent or something similar must be used to provide the pass-phrase if the key is pass-phrase protected.

package main

import (
	"fmt"

	"github.com/apatters/go-run"
)

func main() {
	// Initialize Remote object using defaults.
	runner, _ := run.NewRemote(run.RemoteConfig{
		Credentials: run.Credentials{
			Hostname: "localhost"},
	})

	fmt.Println("Run ls command.")
	stdout, stderr, code, _ := runner.Run(
		"/bin/ls",
		"-1",
		"/bin/true",
		"/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command with an expected error.")
	stdout, stderr, code, _ = runner.Run(
		"/bin/ls",
		"-1",
		"/bin/true",
		"/bin/false",
		"/xyzzy")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	runner, _ = run.NewRemote(run.RemoteConfig{})

	fmt.Println("Run ls command using shell.")
	stdout, stderr, code, _ = runner.Shell("/bin/ls -1 /bin/true /bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run ls command using shell with an expected error.")
	stdout, stderr, code, _ = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run complex shell command.")
	runner, _ = run.NewRemote(run.RemoteConfig{})
	stdout, stderr, code, _ = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()
}

Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n"
exit code = 2

Run ls command using shell.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command using shell with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n"
exit code = 2

Run complex shell command.
stdout = "false\n"
stderr = ""
exit code = 0
Standard runner

The standard runner can be used to run local commands (only) if you do not need to use a customized constructor saving the extra line or two of code needed to call the constructor.

package main

import (
	"fmt"

	"github.com/apatters/go-run"
)

func main() {
	// There is no need for a constructor when running local
	// commands when using the standard runner.
	fmt.Println("Run ls command.")
	stdout, stderr, code, _ := run.Run(
		"/bin/ls",
		"-1",
		"/bin/true",
		"/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	fmt.Println("Run id command.")
	stdout, stderr, code, _ = run.Run("/bin/id", "root")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	stdout, stderr, code, _ = run.Shell("/bin/ls -1 /bin/true /bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	// Print out the command to run, then run it.
	fmt.Println("Run id command.")
	stdout, stderr, code, _ = run.Shell("/bin/id root | cut -f1 -d ' '")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()
}

Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run id command.
stdout = "uid=0(root) gid=0(root) groups=0(root)\n"
stderr = ""
exit code = 0

stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run id command.
stdout = "uid=0(root)\n"
stderr = ""
exit code = 0

License

The go-run package is available under the MITLicense.

Thanks

Thanks to Secure64 for contributing this code.

Documentation

Overview

Package run is a Go (golang) package that wraps the standard Go os/exec and golang.org/x/crypto/ssh packages to run commands either locally or over ssh while capturing stdout, stderr, and exit codes.

Index

Examples

Constants

View Source
const (
	// DefaultShellExecutable is the shell that will be run when
	// using Shell() methods.
	DefaultShellExecutable = "/bin/sh"
)

Variables

This section is empty.

Functions

func FormatRun

func FormatRun(cmd string, args ...string) string

FormatRun returns a string representation of the what command would be run using the standard runner's Run() method. Useful for logging commands.

func FormatShell

func FormatShell(cmd string) string

FormatShell returns a string representation of the what command would be run using the standard runner's Shell() method. Useful for logging commands.

func Run

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

Run runs a command like glibc's exec() call using the standard runner. It returns the standard out, standard error, and exit code of the command when it completes.

Example
// There is no need for a constructor when running local
// commands when using the standard runner.
fmt.Println("Run ls command.")
stdout, stderr, code, err := run.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run id command.")
stdout, stderr, code, err = run.Run("/bin/id", "root")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()
Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run id command.
stdout = "uid=0(root) gid=0(root) groups=0(root)\n"
stderr = ""
exit code = 0

func Shell

func Shell(cmd string) (string, string, int, error)

Shell runs a command in a shell using the standard runner. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes

Example
// There is no need for a constructor when running local
// commands when using the standard runner.
fmt.Println("Run ls command using shell.")
stdout, stderr, code, err := run.Shell("/bin/ls -1 /bin/true /bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

// Print out the command to run, then run it.
fmt.Println("Run id command.")
stdout, stderr, code, err = run.Shell("/bin/id root | cut -f1 -d ' '")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()
Output:

Run ls command using shell.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run id command.
stdout = "uid=0(root)\n"
stderr = ""
exit code = 0

Types

type Credentials

type Credentials struct {
	// Hostname is either the hostname or IP of the remote host.
	Hostname string

	// Port is the port used to connect to the ssh server on the
	// remote host.
	Port int

	// Username is the account name used to authenticate on the
	// remote host.
	Username string

	// Password is password used to authenticate on the remote
	// host. Not needed if using PrivateKeyFilename.
	Password string

	// PrivateKeyFilename is the full path the SSH private key
	// used to authenticate with the remote host.  Not used if
	// Password is specified. You must use ssh-agent or something
	// similar to provide the passphrase if the key is passphrase
	// protected.
	PrivateKeyFilename string
}

Credentials contains needed credentials to SSH to a host. It can use either a password or SSH private key.

type Local

type Local struct {
	// ShellExecutable is the full path to the shell to be run
	// when executing shell commands.
	ShellExecutable string

	// Env specifies the environment of the process.
	// Each entry is of the form "key=value".
	// If Env is nil, the new process uses the current process's
	// environment.
	// If Env contains duplicate environment keys, only the last
	// value in the slice for each duplicate key is used.
	Env []string

	// Dir specifies the working directory of the command.
	// If Dir is the empty string, Run runs the command in the
	// calling process's current directory.
	Dir string

	// Stdin specifies the process's standard input.
	//
	// If Stdin is nil, the process reads from the null device (os.DevNull).
	//
	// If Stdin is an *os.File, the process's standard input is connected
	// directly to that file.
	//
	// Otherwise, during the execution of the command a separate
	// goroutine reads from Stdin and delivers that data to the command
	// over a pipe. In this case, Wait does not complete until the goroutine
	// stops copying, either because it has reached the end of Stdin
	// (EOF or a read error) or because writing to the pipe returned an error.
	Stdin io.Reader

	// Stdout and Stderr specify the process's standard output and error.
	//
	// If either is nil, the descriptor's output is captured and
	// is returned in the Run and Shell functions.
	//
	// If either is an *os.File, the corresponding output from the process
	// is connected directly to that file.
	//
	// Otherwise, during the execution of the command a separate goroutine
	// reads from the process over a pipe and delivers that data to the
	// corresponding Writer. In this case, Wait does not complete until the
	// goroutine reaches EOF or encounters an error.
	//
	// If Stdout and Stderr are the same writer, and have a type that can
	// be compared with ==, at most one goroutine at a time will call Write.
	Stdout io.Writer
	Stderr io.Writer
}

Local wraps os/exec Cmd to make running external commands on the local host relatively as easy as when running them in shell script.

func NewLocal

func NewLocal(config LocalConfig) *Local

NewLocal is the constuctor for Local. It takes a LocalConfig object to configure it. The following configuration options are set if the default LocalConfig constructor, LocalConfig{}, is used:

ShellExecutable = DefaultShellExecutable
Env = []string{} // Use existing environment.
Dir = nil        // Current working directory.
Stdin = nil      // Discard stdin.
Stdout = nil     // Capture stdout.
Stderr = nil     // Capture stderr,

func (*Local) FormatRun

func (l *Local) FormatRun(cmd string, args ...string) string

FormatRun returns a string representation of the what command would be run using Run(). Useful for logging commands.

func (*Local) FormatShell

func (l *Local) FormatShell(cmd string) string

FormatShell returns a string representation of the what command would be run using Shell(). Useful for logging commands.

func (*Local) Run

func (l *Local) Run(cmd string, args ...string) (string, string, int, error)

Run runs a command like glibc's exec() call. It returns the standard out, standard error, and exit code of the command when it completes.

Example
// Initialize Local object using defaults.
runner := run.NewLocal(run.LocalConfig{})

fmt.Println("Run ls command.")
stdout, stderr, code, err := runner.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run ls command with an expected error.")
stdout, stderr, code, err = runner.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false",
	"/xyzzy")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run the ls command with an internal error (bad path).")
_, _, _, err = runner.Run(
	"/not_a_good_path/ls",
	"-1",
	"/bin/true",
	"/bin/false")
fmt.Printf("Internal error executing ls: %s.\n", err)
fmt.Println()

fmt.Println("Run ls command after changing directory.")
runner = run.NewLocal(run.LocalConfig{Dir: "/bin"})
stdout, stderr, code, err = runner.Run(
	"/bin/ls",
	"-1",
	"true",
	"false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()
Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n"
exit code = 2

Run the ls command with an internal error (bad path).
Internal error executing ls: fork/exec /not_a_good_path/ls: no such file or directory.

Run ls command after changing directory.
stdout = "false\ntrue\n"
stderr = ""
exit code = 0

func (*Local) Shell

func (l *Local) Shell(cmd string) (string, string, int, error)

Shell runs a command in a shell. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes.

Example
// Initialize Local object using defaults.
runner := run.NewLocal(run.LocalConfig{})

fmt.Println("Run ls command using shell.")
stdout, stderr, code, err := runner.Shell("/bin/ls -1 /bin/true /bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run ls command using shell with an expected error.")
stdout, stderr, code, err = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run the ls command using shell with an internal error (bad path to shell).")
runner = run.NewLocal(run.LocalConfig{ShellExecutable: "/bin/badsh"})
_, _, _, err = runner.Shell("/bin/ls -1 /bin/true /bin/false")
fmt.Printf("Internal error executing ls: %s.\n", err)
fmt.Println()

fmt.Println("Run ls command using shell after changing directory.")
runner = run.NewLocal(run.LocalConfig{Dir: "/bin"})
stdout, stderr, code, err = runner.Shell("/bin/ls -1 true false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run complex shell command.")
runner = run.NewLocal(run.LocalConfig{})
stdout, stderr, code, err = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()
Output:

Run ls command using shell.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command using shell with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access /xyzzy: No such file or directory\n"
exit code = 2

Run the ls command using shell with an internal error (bad path to shell).
Internal error executing ls: fork/exec : no such file or directory.

Run ls command using shell after changing directory.
stdout = "false\ntrue\n"
stderr = ""
exit code = 0

Run complex shell command.
stdout = "false\n"
stderr = ""
exit code = 0

type LocalConfig

type LocalConfig struct {
	// ShellExecutable is the path to the shell used to run
	// commands in Shell() methods. See Local for details.
	ShellExecutable string

	// Env specifies the environment of the command to be run.
	// See Local for details.
	Env []string

	// Dir specifies the working directory of the command.  See
	// Local for details. The default is the empty string.
	Dir string

	// Stdin specifies the process's standard input. See Local for
	// details.
	Stdin io.Reader

	// Stdout specifies the process's standard output. See Local
	// for details.
	Stdout io.Writer

	// Stderr specifies the process's standard error. See Local
	// for details.
	Stderr io.Writer
}

LocalConfig is used to configure the Local constructor.

type Remote

type Remote struct {
	// ShellExecutable is the full path to the shell on the remote
	// host to be run when executing shell commands.
	ShellExecutable string

	// Stdin specifies the process's standard input.
	//
	// If Stdin is nil, the process reads from the null device (os.DevNull).
	//
	// If Stdin is an *os.File, the process's standard input is connected
	// directly to that file.
	//
	// Otherwise, during the execution of the command a separate
	// goroutine reads from Stdin and delivers that data to the command
	// over a pipe. In this case, Wait does not complete until the goroutine
	// stops copying, either because it has reached the end of Stdin
	// (EOF or a read error) or because writing to the pipe returned an error.
	Stdin io.Reader

	// Stdout and Stderr specify the process's standard output and error.
	//
	// If either is nil, the descriptor's output is captured and
	// is returned in the Run and Shell functions.
	//
	// If either is an *os.File, the corresponding output from the process
	// is connected directly to that file.
	//
	// Otherwise, during the execution of the command a separate goroutine
	// reads from the process over a pipe and delivers that data to the
	// corresponding Writer. In this case, Wait does not complete until the
	// goroutine reaches EOF or encounters an error.
	//
	// If Stdout and Stderr are the same writer, and have a type that can
	// be compared with ==, at most one goroutine at a time will call Write.
	Stdout io.Writer
	Stderr io.Writer

	// Credentials are used to authenticate with the remote host.
	Credentials Credentials
	// contains filtered or unexported fields
}

Remote wraps ssh.Client to make running commands over SSH on a remote host relatively as easy as when running them in shell script.

func NewRemote

func NewRemote(config RemoteConfig) (*Remote, error)

NewRemote is the constructor for Remote. It takes a RemoteConfig object to configure it. The following configuration options are set if the default RemoteConfig constructor, RemoteConfig{}, is used:

ShellExecutable = DefaultShellExecutable
Stdin = nil  // Discard stdin.
Stdout = nil // Capture stdout.
Stderr = nil // Capture stderr,
Credentials.Hostname = "localhost"
Credentials.Port = 22
Credentials.Username = Current user
Credentials.Password = ""
Credentials.PrivateKeyFilename = Current users default private RSA
keyfile ($HOME/.ssh/id_rsa) if present.

func (*Remote) FormatRun

func (r *Remote) FormatRun(cmd string, args ...string) string

FormatRun returns a string representation of the what command would be run using Run(). Useful for logging commands.

func (*Remote) FormatShell

func (r *Remote) FormatShell(cmd string) string

FormatShell returns a string representation of the what command would be run using Shell(). Useful for logging commands.

func (*Remote) Run

func (r *Remote) Run(cmd string, args ...string) (string, string, int, error)

Run runs a command like glibc's exec() call. It returns the standard out, standard error, and exit code of the command when it completes.

Example
// Initialize Remote object using defaults.
runner, _ := run.NewRemote(run.RemoteConfig{
	Credentials: run.Credentials{
		Hostname: "localhost"},
})

fmt.Println("Run ls command.")
stdout, stderr, code, err := runner.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run ls command with an expected error.")
stdout, stderr, code, err = runner.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false",
	"/xyzzy")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run the ls command with an internal error (bad user/passwd).")
runner, _ = run.NewRemote(run.RemoteConfig{
	Credentials: run.Credentials{
		Hostname: "localhost",
		Username: "bad_user",
		Password: "xyzzy"},
})
_, _, _, err = runner.Run(
	"/bin/ls",
	"-1",
	"/bin/true",
	"/bin/false")
fmt.Printf("Internal error executing ls: %s.\n", err)
fmt.Println()
Output:

Run ls command.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n"
exit code = 2

Run the ls command with an internal error (bad user/passwd).
Internal error executing ls: run: connection to bad_user@localhost failed: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain.

func (*Remote) Shell

func (r *Remote) Shell(cmd string) (string, string, int, error)

Shell runs a command in a shell. The command is passed to the shell as the -c option, so just about any shell code that can be used on the command-line will be passed to it. It returns the standard out, standard error, and exit code of the command when it completes.

Example
// Initialize Remote object using defaults.
runner, _ := run.NewRemote(run.RemoteConfig{})

fmt.Println("Run ls command using shell.")
stdout, stderr, code, err := runner.Shell("/bin/ls -1 /bin/true /bin/false")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run ls command using shell with an expected error.")
stdout, stderr, code, err = runner.Shell("/bin/ls -1 /bin/true /bin/false /xyzzy")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()

fmt.Println("Run the ls command using a shell with an internal error (bad user/passwd).")
runner, _ = run.NewRemote(run.RemoteConfig{
	Credentials: run.Credentials{
		Hostname: "localhost",
		Username: "bad_user",
		Password: "xyzzy"},
})
_, _, _, err = runner.Shell("/bin/ls -1 /bin/true /bin/false")
fmt.Printf("Internal error executing ls: %s.\n", err)
fmt.Println()

fmt.Println("Run complex shell command.")
runner, _ = run.NewRemote(run.RemoteConfig{})
stdout, stderr, code, err = runner.Shell("cd /bin && /bin/ls -1 true false | head -n 1")
if err != nil {
	fmt.Printf("Internal error executing ls: %s.\n", err)
	os.Exit(1)
}
fmt.Printf("stdout = %q\n", stdout)
fmt.Printf("stderr = %q\n", stderr)
fmt.Printf("exit code = %d\n", code)
fmt.Println()
Output:

Run ls command using shell.
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Run ls command using shell with an expected error.
stdout = "/bin/false\n/bin/true\n"
stderr = "/bin/ls: cannot access '/xyzzy': No such file or directory\n"
exit code = 2

Run the ls command using a shell with an internal error (bad user/passwd).
Internal error executing ls: run: connection to bad_user@localhost failed: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain.

Run complex shell command.
stdout = "false\n"
stderr = ""
exit code = 0

type RemoteConfig

type RemoteConfig struct {
	// ShellExecutable is the path to the shell on the remote host
	// used to run commands in Shell() methods. See Remote for
	// details.
	ShellExecutable string

	// Stdin specifies the process's standard input. See Remote for
	// details.
	Stdin io.Reader

	// Stdout specifies the process's standard output. See Remote
	// for details.
	Stdout io.Writer

	// Stderr specifies the process's standard error. See Remote
	// for details.
	Stderr io.Writer

	// Credentials used to authenticate on the remote system.
	Credentials Credentials
}

RemoteConfig contains configuration data used in the Remote constructor.

type Runner

type Runner interface {

	// Run runs a command like glibc's exec() call. It returns the
	// standard out, standard error, and exit code of the command
	// when it completes.
	Run(cmd string, args ...string) (string, string, int, error)

	// FormatRun returns a string representation of the what
	// command would be run using Run(). Useful for logging
	// commands.
	FormatRun(cmd string, args ...string) string

	// Shell runs a command in a shell. The command is passed to
	// the shell as the -c option, so just about any shell code
	// that can be used on the command-line will be passed to
	// it. It returns the standard out, standard error, and exit
	// code of the command when it completes
	Shell(cmd string) (string, string, int, error)

	// FormatShell returns a string representation of the what
	// command would be run using Shell(). Useful for logging
	// commands.
	FormatShell(cmd string) string
}

Runner is the interface for both Local and Remote.

Example
// This example shows how to use the run.Runner interface to create
// functions to take either Local or Remote objects. In this example,
// we "log" the command run by the Run() and Shell() methods.

package main

import (
	"fmt"

	"github.com/apatters/go-run"
)

// logRun outputs the command as if run on the command line and then
// "runs" the command using the Run() method. It can take either a
// run.Local or run.Remote object as they both fulfill the run.Runner
// interface.
func logRun(r run.Runner, cmd string, cmdArgs ...string) (stdout string, stderr string, exitCode int, err error) {
	fmt.Printf("%s\n", r.FormatRun(cmd, cmdArgs...))
	return r.Run(cmd, cmdArgs...)
}

// logShell outputs the shell command as if run on the command line
// and then "runs" the command using the Shell() method. It can take
// either a run.Local or run.Remote object as they both fulfill the
// run.Runner interface.
func logShell(r run.Runner, cmd string) (stdout string, stderr string, exitCode int, err error) {
	fmt.Printf("%s\n", r.FormatShell(cmd))
	return r.Shell(cmd)
}

func main() {
	l := run.NewLocal(run.LocalConfig{})
	r, _ := run.NewRemote(run.RemoteConfig{
		Credentials: run.Credentials{
			Hostname: "localhost",
			Username: "buildman"},
	})

	stdout, stderr, code, _ := logRun(l, "/bin/ls", "-1", "/bin/true", "/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	stdout, stderr, code, _ = logRun(r, "/bin/ls", "-1", "/bin/true", "/bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	stdout, stderr, code, _ = logShell(l, "/bin/ls -1 /bin/true /bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

	stdout, stderr, code, _ = logShell(r, "/bin/ls -1 /bin/true /bin/false")
	fmt.Printf("stdout = %q\n", stdout)
	fmt.Printf("stderr = %q\n", stderr)
	fmt.Printf("exit code = %d\n", code)
	fmt.Println()

}
Output:

/bin/ls -1 /bin/true /bin/false
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

ssh buildman@localhost /bin/ls -1 /bin/true /bin/false
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

/bin/sh -c "/bin/ls -1 /bin/true /bin/false"
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

ssh buildman@localhost /bin/sh -c "/bin/ls -1 /bin/true /bin/false"
stdout = "/bin/false\n/bin/true\n"
stderr = ""
exit code = 0

Jump to

Keyboard shortcuts

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