interp

package
v0.0.0-...-34eb8de Latest Latest
Warning

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

Go to latest
Published: Feb 14, 2024 License: BSD-3-Clause Imports: 28 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.

The interpreter generally aims to behave like Bash, but it does not support all of its features.

The interpreter currently aims to behave like a non-interactive shell, which is how most shells run scripts, and is more useful to machines. In the future, it may gain an option to behave like an interactive shell.

Example
package main

import (
	"context"
	"os"
	"strings"

	"mvdan.cc/sh/v3/expand"
	"mvdan.cc/sh/v3/interp"
	"mvdan.cc/sh/v3/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

This section is empty.

Functions

func IsExitStatus

func IsExitStatus(err error) (status uint8, ok bool)

IsExitStatus checks whether error contains an exit status and returns it.

func LookPath

func LookPath(env expand.Environ, file string) (string, error)

LookPath is deprecated; see LookPathDir.

func LookPathDir

func LookPathDir(cwd string, env expand.Environ, file string) (string, error)

LookPathDir is similar to os/exec.LookPath, with the difference that it uses the provided environment. env is used to fetch relevant environment variables such as PWD and PATH.

If no error is returned, the returned path must be valid.

func NewExitStatus

func NewExitStatus(status uint8) error

NewExitStatus creates an error which contains the specified exit status code.

Types

type CallHandlerFunc

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

CallHandlerFunc is a handler which runs on every syntax.CallExpr. It is called once variable assignments and field expansion have occurred. The call's arguments are replaced by what the handler returns, and then the call is executed by the Runner as usual. At this time, returning an empty slice without an error is not supported.

This handler is similar to ExecHandlerFunc, but has two major differences:

First, it runs for all simple commands, including function calls and builtins.

Second, it is not expected to execute the simple command, but instead to allow running custom code which allows replacing the argument list. Shell builtins touch on many internals of the Runner, after all.

Returning a non-nil error will halt the Runner.

type ExecHandlerFunc

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

ExecHandlerFunc is a handler which executes simple commands. It is called for all syntax.CallExpr nodes where the first argument is neither a declared function nor a builtin.

Returning a nil error means a zero exit status. Other exit statuses can be set with NewExitStatus. Any other error will halt the Runner.

func DefaultExecHandler

func DefaultExecHandler(killTimeout time.Duration) ExecHandlerFunc

DefaultExecHandler returns the ExecHandlerFunc used by default. It finds binaries in PATH and executes them. When context is cancelled, an interrupt signal is sent to running processes. killTimeout is a duration to wait before sending the kill signal. 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. Runner defaults to a killTimeout of 2 seconds.

type HandlerContext

type HandlerContext struct {
	// Env is a read-only version of the interpreter's environment,
	// including environment variables, global variables, and local function
	// variables.
	Env expand.Environ

	// Dir is the interpreter's current directory.
	Dir string

	// Stdin is the interpreter's current standard input reader.
	Stdin io.Reader
	// Stdout is the interpreter's current standard output writer.
	Stdout io.Writer
	// Stderr is the interpreter's current standard error writer.
	Stderr io.Writer
}

HandlerContext is the data passed to all the handler functions via context.WithValue. It contains some of the current state of the Runner.

func HandlerCtx

func HandlerCtx(ctx context.Context) HandlerContext

HandlerCtx returns HandlerContext value stored in ctx. It panics if ctx has no HandlerContext stored.

type OpenHandlerFunc

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

OpenHandlerFunc is a handler which opens files. It is called 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 may be relative to the current directory, which can be fetched via HandlerCtx.

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.

func DefaultOpenHandler

func DefaultOpenHandler() OpenHandlerFunc

DefaultOpenHandler returns the OpenHandlerFunc used by default. It uses os.OpenFile to open files.

type ReadDirHandlerFunc

type ReadDirHandlerFunc func(ctx context.Context, path string) ([]fs.FileInfo, error)

ReadDirHandlerFunc is a handler which reads directories. It is called during shell globbing, if enabled.

TODO(v4): if this is kept in v4, it most likely needs to use io/fs.DirEntry for efficiency

func DefaultReadDirHandler

func DefaultReadDirHandler() ReadDirHandlerFunc

DefaultReadDirHandler returns the ReadDirHandlerFunc used by default. It makes use of ioutil.ReadDir.

type ReadDirHandlerFunc2

type ReadDirHandlerFunc2 func(ctx context.Context, path string) ([]fs.DirEntry, error)

func DefaultReadDirHandler2

func DefaultReadDirHandler2() ReadDirHandlerFunc2

DefaultReadDirHandler2 returns the ReadDirHandlerFunc2 used by default. It uses os.ReadDir.

type Runner

type Runner struct {
	// Env specifies the initial environment for 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

	Vars  map[string]expand.Variable
	Funcs map[string]*syntax.Stmt
	// contains filtered or unexported fields
}

A Runner interprets shell programs. It can be reused, but it is not safe for concurrent use. 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.

Runner's exported fields are meant to be configured via RunnerOption; once a Runner has been created, the fields should be treated as read-only.

func New

func New(opts ...RunnerOption) (*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) Exited

func (r *Runner) Exited() bool

Exited reports whether the last Run call should exit an entire shell. This can be triggered by the "exit" built-in command, for example.

Note that this state is overwritten at every Run call, so it should be checked immediately after each Run call.

func (*Runner) Reset

func (r *Runner) Reset()

Reset returns a runner to its initial state, right before the first call to Run or Reset.

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, options, and the current directory.

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 contain a command's exit status, which can be retrieved with IsExitStatus.

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

Calling Run on an entire *File implies an exit, meaning that an exit trap may run.

func (*Runner) Subshell

func (r *Runner) Subshell() *Runner

Subshell makes a copy of the given Runner, suitable for use concurrently with the original. The copy will have the same environment, including variables and functions, but they can all be modified without affecting the original.

Subshell is not safe to use concurrently with Run. Orchestrating this is left up to the caller; no locking is performed.

To replace e.g. stdin/out/err, do StdIO(r.stdin, r.stdout, r.stderr)(r) on the copy.

type RunnerOption

type RunnerOption func(*Runner) error

RunnerOption can be passed to New to alter a Runner's behaviour. It can also be applied directly on an existing Runner, such as interp.Params("-e")(runner). Note that options cannot be applied once Run or Reset have been called. TODO: enforce that rule via didReset.

func CallHandler

func CallHandler(f CallHandlerFunc) RunnerOption

CallHandler sets the call handler. See CallHandlerFunc for more info.

func Dir

func Dir(path string) RunnerOption

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

func Env

func Env(env expand.Environ) RunnerOption

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

func ExecHandler deprecated

func ExecHandler(f ExecHandlerFunc) RunnerOption

ExecHandler sets one command execution handler, which replaces DefaultExecHandler(2 * time.Second).

Deprecated: use ExecHandlers instead, which allows for middleware handlers.

func ExecHandlers

func ExecHandlers(middlewares ...func(next ExecHandlerFunc) ExecHandlerFunc) RunnerOption

ExecHandlers appends middlewares to handle command execution. The middlewares are chained from first to last, and the first is called by the runner. Each middleware is expected to call the "next" middleware at most once.

For example, a middleware may implement only some commands. For those commands, it can run its logic and avoid calling "next". For any other commands, it can call "next" with the original parameters.

Another common example is a middleware which always calls "next", but runs custom logic either before or after that call. For instance, a middleware could change the arguments to the "next" call, or it could print log lines before or after the call to "next".

The last exec handler is DefaultExecHandler(2 * time.Second).

Example
package main

import (
	"context"
	"fmt"
	"os"
	"strings"

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

func main() {
	src := "echo foo; join ! foo bar baz; missing-program bar"
	file, _ := syntax.NewParser().Parse(strings.NewReader(src), "")

	execJoin := func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
		return func(ctx context.Context, args []string) error {
			hc := interp.HandlerCtx(ctx)
			if args[0] == "join" {
				fmt.Fprintln(hc.Stdout, strings.Join(args[2:], args[1]))
				return nil
			}
			return next(ctx, args)
		}
	}
	execNotInstalled := func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
		return func(ctx context.Context, args []string) error {
			hc := interp.HandlerCtx(ctx)
			if _, err := interp.LookPathDir(hc.Dir, hc.Env, args[0]); err != nil {
				fmt.Printf("%s is not installed\n", args[0])
				return interp.NewExitStatus(1)
			}
			return next(ctx, args)
		}
	}
	runner, _ := interp.New(
		interp.StdIO(nil, os.Stdout, os.Stdout),
		interp.ExecHandlers(execJoin, execNotInstalled),
	)
	runner.Run(context.TODO(), file)
}
Output:

foo
foo!bar!baz
missing-program is not installed

func OpenHandler

func OpenHandler(f OpenHandlerFunc) RunnerOption

OpenHandler sets file open handler. See OpenHandlerFunc for more info.

Example
package main

import (
	"context"
	"io"
	"os"
	"runtime"
	"strings"

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

func main() {
	src := "echo foo; echo bar >/dev/null"
	file, _ := syntax.NewParser().Parse(strings.NewReader(src), "")

	open := func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
		if runtime.GOOS == "windows" && path == "/dev/null" {
			path = "NUL"
		}
		return interp.DefaultOpenHandler()(ctx, path, flag, perm)
	}
	runner, _ := interp.New(
		interp.StdIO(nil, os.Stdout, os.Stdout),
		interp.OpenHandler(open),
	)
	runner.Run(context.TODO(), file)
}
Output:

foo

func Params

func Params(args ...string) RunnerOption

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

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

func ReadDirHandler deprecated

func ReadDirHandler(f ReadDirHandlerFunc) RunnerOption

ReadDirHandler sets the read directory handler. See ReadDirHandlerFunc for more info.

Deprecated: use ReadDirHandler2.

func ReadDirHandler2

func ReadDirHandler2(f ReadDirHandlerFunc2) RunnerOption

ReadDirHandler2 sets the read directory handler. See ReadDirHandlerFunc2 for more info.

func StatHandler

func StatHandler(f StatHandlerFunc) RunnerOption

StatHandler sets the stat handler. See StatHandlerFunc for more info.

func StdIO

func StdIO(in io.Reader, out, err io.Writer) RunnerOption

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.

type StatHandlerFunc

type StatHandlerFunc func(ctx context.Context, name string, followSymlinks bool) (fs.FileInfo, error)

StatHandlerFunc is a handler which gets a file's information.

func DefaultStatHandler

func DefaultStatHandler() StatHandlerFunc

DefaultStatHandler returns the StatHandlerFunc used by default. It makes use of os.Stat and os.Lstat, depending on followSymlinks.

Jump to

Keyboard shortcuts

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