Categorygithub.com/apatters/go-run
repositorypackage
1.0.1
Repository: https://github.com/apatters/go-run.git
Documentation: pkg.go.dev

# 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.