interp

package
v2.6.4 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2019 License: BSD-3-Clause Imports: 21 Imported by: 0

Documentation

Overview

Package interp implements an interpreter that executes shell programs. It aims to support POSIX, but its support is not complete yet. It also supports some Bash features.

Example
package main

import (
	"context"
	"os"
	"strings"

	"mvdan.cc/sh/expand"
	"mvdan.cc/sh/interp"
	"mvdan.cc/sh/syntax"
)

func main() {
	src := `
		foo=abc
		for i in 1 2 3; do
			foo+=$i
		done
		let bar=(2 + 3)
		echo $foo $bar
		echo $GLOBAL
	`
	file, _ := syntax.NewParser().Parse(strings.NewReader(src), "")
	runner, _ := interp.New(
		interp.Env(expand.ListEnviron("GLOBAL=global_value")),
		interp.StdIO(nil, os.Stdout, os.Stdout),
	)
	runner.Run(context.TODO(), file)
}
Output:

abc123 5
global_value

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultExec = ModuleExec(func(ctx context.Context, path string, args []string) error {
	mc, _ := FromModuleContext(ctx)
	if path == "" {
		fmt.Fprintf(mc.Stderr, "%q: executable file not found in $PATH\n", args[0])
		return ExitStatus(127)
	}
	cmd := exec.Cmd{
		Path:   path,
		Args:   args,
		Env:    execEnv(mc.Env),
		Dir:    mc.Dir,
		Stdin:  mc.Stdin,
		Stdout: mc.Stdout,
		Stderr: mc.Stderr,
	}

	err := cmd.Start()
	if err == nil {
		if done := ctx.Done(); done != nil {
			go func() {
				<-done

				if mc.KillTimeout <= 0 || runtime.GOOS == "windows" {
					_ = cmd.Process.Signal(os.Kill)
					return
				}

				go func() {
					time.Sleep(mc.KillTimeout)
					_ = cmd.Process.Signal(os.Kill)
				}()
				_ = cmd.Process.Signal(os.Interrupt)
			}()
		}

		err = cmd.Wait()
	}

	switch x := err.(type) {
	case *exec.ExitError:

		if status, ok := x.Sys().(syscall.WaitStatus); ok {
			if status.Signaled() && ctx.Err() != nil {
				return ctx.Err()
			}
			return ExitStatus(status.ExitStatus())
		}
		return ExitStatus(1)
	case *exec.Error:

		fmt.Fprintf(mc.Stderr, "%v\n", err)
		return ExitStatus(127)
	default:
		return err
	}
})
View Source
var DefaultOpen = ModuleOpen(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
	return os.OpenFile(path, flag, perm)
})

Functions

func Dir added in v2.6.0

func Dir(path string) func(*Runner) error

Dir sets the interpreter's working directory. If empty, the process's current directory is used.

func Env added in v2.6.0

func Env(env expand.Environ) func(*Runner) error

Env sets the interpreter's environment. If nil, a copy of the current process's environment is used.

func Module added in v2.6.0

func Module(mod ModuleFunc) func(*Runner) error

Module sets an interpreter module, which can be ModuleExec or ModuleOpen. If the value is nil, the default module implementation is used.

func Params added in v2.6.0

func Params(args ...string) func(*Runner) error

Params populates the shell options and parameters. For example, Params("-e", "--", "foo") will set the "-e" option and the parameters ["foo"].

This is similar to what the interpreter's "set" builtin does.

func StdIO added in v2.6.0

func StdIO(in io.Reader, out, err io.Writer) func(*Runner) error

StdIO configures an interpreter's standard input, standard output, and standard error. If out or err are nil, they default to a writer that discards the output.

Types

type ExitStatus added in v2.6.0

type ExitStatus uint8

ExitStatus is a non-zero status code resulting from running a shell node.

func (ExitStatus) Error added in v2.6.0

func (s ExitStatus) Error() string

type ModuleCtx added in v2.6.0

type ModuleCtx struct {
	Env         expand.Environ
	Dir         string
	Stdin       io.Reader
	Stdout      io.Writer
	Stderr      io.Writer
	KillTimeout time.Duration
}

ModuleCtx is the data passed to all the module functions via a context value. It contains some of the current state of the Runner, as well as some fields necessary to implement some of the modules.

func FromModuleContext added in v2.6.0

func FromModuleContext(ctx context.Context) (ModuleCtx, bool)

FromModuleContext returns the ModuleCtx value stored in ctx, if any.

func (ModuleCtx) UnixPath added in v2.6.0

func (mc ModuleCtx) UnixPath(path string) string

UnixPath fixes absolute unix paths on Windows, for example converting "C:\\CurDir\\dev\\null" to "/dev/null".

type ModuleExec added in v2.1.0

type ModuleExec func(ctx context.Context, path string, args []string) error

ModuleExec is the module responsible for executing a program. It is executed for all CallExpr nodes where the first argument is neither a declared function nor a builtin.

Note that the name is included as the first argument. If path is an empty string, it means that the executable did not exist or was not found in $PATH.

Use a return error of type ExitStatus to set the exit status. A nil error has the same effect as ExitStatus(0). If the error is of any other type, the interpreter will come to a stop.

Example
package main

import (
	"context"
	"os"
	"strings"

	"mvdan.cc/sh/interp"
	"mvdan.cc/sh/syntax"
)

func main() {
	src := `
		ls example_test.* || echo "ls failed"
		rm example_test.* || echo "rm failed"
	`
	file, _ := syntax.NewParser().Parse(strings.NewReader(src), "")
	exec := func(ctx context.Context, path string, args []string) error {
		switch args[0] {
		case "ls":
			// whitelist the "ls" program
			return interp.DefaultExec(ctx, path, args)
		default:
			// refuse to run any other program
			return interp.ExitStatus(1)
		}
	}
	runner, _ := interp.New(
		interp.StdIO(nil, os.Stdout, os.Stdout),
		interp.Module(interp.ModuleExec(exec)),
	)
	runner.Run(context.TODO(), file)
}
Output:

example_test.go
rm failed

type ModuleFunc added in v2.6.0

type ModuleFunc interface {
	// contains filtered or unexported methods
}

type ModuleOpen added in v2.1.0

type ModuleOpen func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)

ModuleOpen is the module responsible for opening a file. It is executed for all files that are opened directly by the shell, such as in redirects. Files opened by executed programs are not included.

The path parameter is absolute and has been cleaned.

Use a return error of type *os.PathError to have the error printed to stderr and the exit status set to 1. If the error is of any other type, the interpreter will come to a stop.

TODO: What about stat calls? They are used heavily in the builtin test expressions, and also when doing a cd. Should they have a separate module?

func OpenDevImpls added in v2.1.0

func OpenDevImpls(next ModuleOpen) ModuleOpen

type Runner

type Runner struct {
	// Env specifies the environment of the interpreter, which must be
	// non-nil.
	Env expand.Environ

	// Dir specifies the working directory of the command, which must be an
	// absolute path.
	Dir string

	// Params are the current shell parameters, e.g. from running a shell
	// file or calling a function. Accessible via the $@/$* family of vars.
	Params []string

	// Exec is the module responsible for executing programs. It must be
	// non-nil.
	Exec ModuleExec
	// Open is the module responsible for opening files. It must be non-nil.
	Open ModuleOpen

	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	Vars  map[string]expand.Variable
	Funcs map[string]*syntax.Stmt

	// KillTimeout holds how much time the interpreter will wait for a
	// program to stop after being sent an interrupt signal, after
	// which a kill signal will be sent. This process will happen when the
	// interpreter's context is cancelled.
	//
	// The zero value will default to 2 seconds.
	//
	// A negative value means that a kill signal will be sent immediately.
	//
	// On Windows, the kill signal is always sent immediately,
	// because Go doesn't currently support sending Interrupt on Windows.
	KillTimeout time.Duration
	// contains filtered or unexported fields
}

A Runner interprets shell programs. It can be reused, but it is not safe for concurrent use. You should typically use New to build a new Runner.

Note that writes to Stdout and Stderr may be concurrent if background commands are used. If you plan on using an io.Writer implementation that isn't safe for concurrent use, consider a workaround like hiding writes behind a mutex.

To create a Runner, use New.

func New added in v2.6.0

func New(opts ...func(*Runner) error) (*Runner, error)

New creates a new Runner, applying a number of options. If applying any of the options results in an error, it is returned.

Any unset options fall back to their defaults. For example, not supplying the environment falls back to the process's environment, and not supplying the standard output writer means that the output will be discarded.

func (*Runner) Reset

func (r *Runner) Reset()

Reset empties the runner state and sets any exported fields with zero values to their default values.

Typically, this function only needs to be called if a runner is reused to run multiple programs non-incrementally. Not calling Reset between each run will mean that the shell state will be kept, including variables and options.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, node syntax.Node) error

Run interprets a node, which can be a *File, *Stmt, or Command. If a non-nil error is returned, it will typically be of type ExitStatus or ShellExitStatus.

Run can be called multiple times synchronously to interpret programs incrementally. To reuse a Runner without keeping the internal shell state, call Reset.

type ShellExitStatus added in v2.6.0

type ShellExitStatus uint8

ShellExitStatus exits the shell with a status code.

func (ShellExitStatus) Error added in v2.6.0

func (s ShellExitStatus) Error() string

Jump to

Keyboard shortcuts

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