github.com/cilki/sh@v2.6.4+incompatible/interp/interp.go (about)

     1  // Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package interp
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"math"
    13  	"os"
    14  	"os/user"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"golang.org/x/sync/errgroup"
    23  
    24  	"mvdan.cc/sh/expand"
    25  	"mvdan.cc/sh/syntax"
    26  )
    27  
    28  // New creates a new Runner, applying a number of options. If applying any of
    29  // the options results in an error, it is returned.
    30  //
    31  // Any unset options fall back to their defaults. For example, not supplying the
    32  // environment falls back to the process's environment, and not supplying the
    33  // standard output writer means that the output will be discarded.
    34  func New(opts ...func(*Runner) error) (*Runner, error) {
    35  	r := &Runner{usedNew: true}
    36  	for _, opt := range opts {
    37  		if err := opt(r); err != nil {
    38  			return nil, err
    39  		}
    40  	}
    41  	// Set the default fallbacks, if necessary.
    42  	if r.Env == nil {
    43  		Env(nil)(r)
    44  	}
    45  	if r.Dir == "" {
    46  		if err := Dir("")(r); err != nil {
    47  			return nil, err
    48  		}
    49  	}
    50  	if r.Exec == nil {
    51  		Module(ModuleExec(nil))(r)
    52  	}
    53  	if r.Open == nil {
    54  		Module(ModuleOpen(nil))(r)
    55  	}
    56  	if r.Stdout == nil || r.Stderr == nil {
    57  		StdIO(r.Stdin, r.Stdout, r.Stderr)(r)
    58  	}
    59  	return r, nil
    60  }
    61  
    62  func (r *Runner) fillExpandConfig(ctx context.Context) {
    63  	r.ectx = ctx
    64  	r.ecfg = &expand.Config{
    65  		Env: expandEnv{r},
    66  		CmdSubst: func(w io.Writer, cs *syntax.CmdSubst) error {
    67  			switch len(cs.Stmts) {
    68  			case 0: // nothing to do
    69  				return nil
    70  			case 1: // $(<file)
    71  				word := catShortcutArg(cs.Stmts[0])
    72  				if word == nil {
    73  					break
    74  				}
    75  				path := r.literal(word)
    76  				f, err := r.open(ctx, r.relPath(path), os.O_RDONLY, 0, true)
    77  				if err != nil {
    78  					return err
    79  				}
    80  				_, err = io.Copy(w, f)
    81  				return err
    82  			}
    83  			r2 := r.sub()
    84  			r2.Stdout = w
    85  			r2.stmts(ctx, cs.StmtList)
    86  			return r2.err
    87  		},
    88  		ReadDir: ioutil.ReadDir,
    89  	}
    90  	r.updateExpandOpts()
    91  }
    92  
    93  // catShortcutArg checks if a statement is of the form "$(<file)". The redirect
    94  // word is returned if there's a match, and nil otherwise.
    95  func catShortcutArg(stmt *syntax.Stmt) *syntax.Word {
    96  	if stmt.Cmd != nil || stmt.Negated || stmt.Background || stmt.Coprocess {
    97  		return nil
    98  	}
    99  	if len(stmt.Redirs) != 1 {
   100  		return nil
   101  	}
   102  	redir := stmt.Redirs[0]
   103  	if redir.Op != syntax.RdrIn {
   104  		return nil
   105  	}
   106  	return redir.Word
   107  }
   108  
   109  func (r *Runner) updateExpandOpts() {
   110  	r.ecfg.NoGlob = r.opts[optNoGlob]
   111  	r.ecfg.GlobStar = r.opts[optGlobStar]
   112  }
   113  
   114  func (r *Runner) expandErr(err error) {
   115  	switch err := err.(type) {
   116  	case nil:
   117  	case expand.UnsetParameterError:
   118  		r.errf("%s\n", err.Message)
   119  		r.exit = 1
   120  		r.setErr(ShellExitStatus(r.exit))
   121  	default:
   122  		r.setErr(err)
   123  		r.exit = 1
   124  	}
   125  }
   126  
   127  func (r *Runner) arithm(expr syntax.ArithmExpr) int {
   128  	n, err := expand.Arithm(r.ecfg, expr)
   129  	r.expandErr(err)
   130  	return n
   131  }
   132  
   133  func (r *Runner) fields(words ...*syntax.Word) []string {
   134  	strs, err := expand.Fields(r.ecfg, words...)
   135  	r.expandErr(err)
   136  	return strs
   137  }
   138  
   139  func (r *Runner) literal(word *syntax.Word) string {
   140  	str, err := expand.Literal(r.ecfg, word)
   141  	r.expandErr(err)
   142  	return str
   143  }
   144  
   145  func (r *Runner) document(word *syntax.Word) string {
   146  	str, err := expand.Document(r.ecfg, word)
   147  	r.expandErr(err)
   148  	return str
   149  }
   150  
   151  func (r *Runner) pattern(word *syntax.Word) string {
   152  	str, err := expand.Pattern(r.ecfg, word)
   153  	r.expandErr(err)
   154  	return str
   155  }
   156  
   157  // expandEnv exposes Runner's variables to the expand package.
   158  type expandEnv struct {
   159  	r *Runner
   160  }
   161  
   162  func (e expandEnv) Get(name string) expand.Variable {
   163  	return e.r.lookupVar(name)
   164  }
   165  func (e expandEnv) Set(name string, vr expand.Variable) {
   166  	e.r.setVarInternal(name, vr)
   167  }
   168  func (e expandEnv) Each(fn func(name string, vr expand.Variable) bool) {
   169  	e.r.Env.Each(fn)
   170  	for name, vr := range e.r.Vars {
   171  		if !fn(name, vr) {
   172  			return
   173  		}
   174  	}
   175  }
   176  
   177  // Env sets the interpreter's environment. If nil, a copy of the current
   178  // process's environment is used.
   179  func Env(env expand.Environ) func(*Runner) error {
   180  	return func(r *Runner) error {
   181  		if env == nil {
   182  			env = expand.ListEnviron(os.Environ()...)
   183  		}
   184  		r.Env = env
   185  		return nil
   186  	}
   187  }
   188  
   189  // Dir sets the interpreter's working directory. If empty, the process's current
   190  // directory is used.
   191  func Dir(path string) func(*Runner) error {
   192  	return func(r *Runner) error {
   193  		if path == "" {
   194  			path, err := os.Getwd()
   195  			if err != nil {
   196  				return fmt.Errorf("could not get current dir: %v", err)
   197  			}
   198  			r.Dir = path
   199  			return nil
   200  		}
   201  		path, err := filepath.Abs(path)
   202  		if err != nil {
   203  			return fmt.Errorf("could not get absolute dir: %v", err)
   204  		}
   205  		info, err := os.Stat(path)
   206  		if err != nil {
   207  			return fmt.Errorf("could not stat: %v", err)
   208  		}
   209  		if !info.IsDir() {
   210  			return fmt.Errorf("%s is not a directory", path)
   211  		}
   212  		r.Dir = path
   213  		return nil
   214  	}
   215  }
   216  
   217  // Params populates the shell options and parameters. For example, Params("-e",
   218  // "--", "foo") will set the "-e" option and the parameters ["foo"].
   219  //
   220  // This is similar to what the interpreter's "set" builtin does.
   221  func Params(args ...string) func(*Runner) error {
   222  	return func(r *Runner) error {
   223  		for len(args) > 0 {
   224  			arg := args[0]
   225  			if arg == "" || (arg[0] != '-' && arg[0] != '+') {
   226  				break
   227  			}
   228  			if arg == "--" {
   229  				args = args[1:]
   230  				break
   231  			}
   232  			enable := arg[0] == '-'
   233  			var opt *bool
   234  			if flag := arg[1:]; flag == "o" {
   235  				args = args[1:]
   236  				if len(args) == 0 && enable {
   237  					for i, opt := range &shellOptsTable {
   238  						r.printOptLine(opt.name, r.opts[i])
   239  					}
   240  					break
   241  				}
   242  				if len(args) == 0 && !enable {
   243  					for i, opt := range &shellOptsTable {
   244  						setFlag := "+o"
   245  						if r.opts[i] {
   246  							setFlag = "-o"
   247  						}
   248  						r.outf("set %s %s\n", setFlag, opt.name)
   249  					}
   250  					break
   251  				}
   252  				opt = r.optByName(args[0], false)
   253  			} else {
   254  				opt = r.optByFlag(flag)
   255  			}
   256  			if opt == nil {
   257  				return fmt.Errorf("invalid option: %q", arg)
   258  			}
   259  			*opt = enable
   260  			args = args[1:]
   261  		}
   262  		r.Params = args
   263  		return nil
   264  	}
   265  }
   266  
   267  type ModuleFunc interface {
   268  	isModule()
   269  }
   270  
   271  // Module sets an interpreter module, which can be ModuleExec or ModuleOpen. If
   272  // the value is nil, the default module implementation is used.
   273  func Module(mod ModuleFunc) func(*Runner) error {
   274  	return func(r *Runner) error {
   275  		switch mod := mod.(type) {
   276  		case ModuleExec:
   277  			if mod == nil {
   278  				mod = DefaultExec
   279  			}
   280  			r.Exec = mod
   281  		case ModuleOpen:
   282  			if mod == nil {
   283  				mod = DefaultOpen
   284  			}
   285  			r.Open = mod
   286  		default:
   287  			return fmt.Errorf("unknown module type: %T", mod)
   288  		}
   289  		return nil
   290  	}
   291  }
   292  
   293  // StdIO configures an interpreter's standard input, standard output, and
   294  // standard error. If out or err are nil, they default to a writer that discards
   295  // the output.
   296  func StdIO(in io.Reader, out, err io.Writer) func(*Runner) error {
   297  	return func(r *Runner) error {
   298  		r.Stdin = in
   299  		if out == nil {
   300  			out = ioutil.Discard
   301  		}
   302  		r.Stdout = out
   303  		if err == nil {
   304  			err = ioutil.Discard
   305  		}
   306  		r.Stderr = err
   307  		return nil
   308  	}
   309  }
   310  
   311  // A Runner interprets shell programs. It can be reused, but it is not safe for
   312  // concurrent use. You should typically use New to build a new Runner.
   313  //
   314  // Note that writes to Stdout and Stderr may be concurrent if background
   315  // commands are used. If you plan on using an io.Writer implementation that
   316  // isn't safe for concurrent use, consider a workaround like hiding writes
   317  // behind a mutex.
   318  //
   319  // To create a Runner, use New.
   320  type Runner struct {
   321  	// Env specifies the environment of the interpreter, which must be
   322  	// non-nil.
   323  	Env expand.Environ
   324  
   325  	// Dir specifies the working directory of the command, which must be an
   326  	// absolute path.
   327  	Dir string
   328  
   329  	// Params are the current shell parameters, e.g. from running a shell
   330  	// file or calling a function. Accessible via the $@/$* family of vars.
   331  	Params []string
   332  
   333  	// Exec is the module responsible for executing programs. It must be
   334  	// non-nil.
   335  	Exec ModuleExec
   336  	// Open is the module responsible for opening files. It must be non-nil.
   337  	Open ModuleOpen
   338  
   339  	Stdin  io.Reader
   340  	Stdout io.Writer
   341  	Stderr io.Writer
   342  
   343  	// Separate maps - note that bash allows a name to be both a var and a
   344  	// func simultaneously
   345  
   346  	Vars  map[string]expand.Variable
   347  	Funcs map[string]*syntax.Stmt
   348  
   349  	ecfg *expand.Config
   350  	ectx context.Context // just so that Runner.Sub can use it again
   351  
   352  	// didReset remembers whether the runner has ever been reset. This is
   353  	// used so that Reset is automatically called when running any program
   354  	// or node for the first time on a Runner.
   355  	didReset bool
   356  
   357  	usedNew bool
   358  
   359  	filename string // only if Node was a File
   360  
   361  	// like Vars, but local to a func i.e. "local foo=bar"
   362  	funcVars map[string]expand.Variable
   363  
   364  	// like Vars, but local to a cmd i.e. "foo=bar prog args..."
   365  	cmdVars map[string]string
   366  
   367  	// >0 to break or continue out of N enclosing loops
   368  	breakEnclosing, contnEnclosing int
   369  
   370  	inLoop   bool
   371  	inFunc   bool
   372  	inSource bool
   373  
   374  	err  error // current shell exit code or fatal error
   375  	exit int   // current (last) exit status code
   376  
   377  	bgShells errgroup.Group
   378  
   379  	opts [len(shellOptsTable) + len(bashOptsTable)]bool
   380  
   381  	dirStack []string
   382  
   383  	optState getopts
   384  
   385  	// keepRedirs is used so that "exec" can make any redirections
   386  	// apply to the current shell, and not just the command.
   387  	keepRedirs bool
   388  
   389  	// KillTimeout holds how much time the interpreter will wait for a
   390  	// program to stop after being sent an interrupt signal, after
   391  	// which a kill signal will be sent. This process will happen when the
   392  	// interpreter's context is cancelled.
   393  	//
   394  	// The zero value will default to 2 seconds.
   395  	//
   396  	// A negative value means that a kill signal will be sent immediately.
   397  	//
   398  	// On Windows, the kill signal is always sent immediately,
   399  	// because Go doesn't currently support sending Interrupt on Windows.
   400  	KillTimeout time.Duration
   401  }
   402  
   403  func (r *Runner) optByFlag(flag string) *bool {
   404  	for i, opt := range &shellOptsTable {
   405  		if opt.flag == flag {
   406  			return &r.opts[i]
   407  		}
   408  	}
   409  	return nil
   410  }
   411  
   412  func (r *Runner) optByName(name string, bash bool) *bool {
   413  	if bash {
   414  		for i, optName := range bashOptsTable {
   415  			if optName == name {
   416  				return &r.opts[len(shellOptsTable)+i]
   417  			}
   418  		}
   419  	}
   420  	for i, opt := range &shellOptsTable {
   421  		if opt.name == name {
   422  			return &r.opts[i]
   423  		}
   424  	}
   425  	return nil
   426  }
   427  
   428  var shellOptsTable = [...]struct {
   429  	flag, name string
   430  }{
   431  	// sorted alphabetically by name; use a space for the options
   432  	// that have no flag form
   433  	{"a", "allexport"},
   434  	{"e", "errexit"},
   435  	{"n", "noexec"},
   436  	{"f", "noglob"},
   437  	{"u", "nounset"},
   438  	{" ", "pipefail"},
   439  }
   440  
   441  var bashOptsTable = [...]string{
   442  	// sorted alphabetically by name
   443  	"globstar",
   444  }
   445  
   446  // To access the shell options arrays without a linear search when we
   447  // know which option we're after at compile time. First come the shell options,
   448  // then the bash options.
   449  const (
   450  	optAllExport = iota
   451  	optErrExit
   452  	optNoExec
   453  	optNoGlob
   454  	optNoUnset
   455  	optPipeFail
   456  
   457  	optGlobStar
   458  )
   459  
   460  // Reset empties the runner state and sets any exported fields with zero values
   461  // to their default values.
   462  //
   463  // Typically, this function only needs to be called if a runner is reused to run
   464  // multiple programs non-incrementally. Not calling Reset between each run will
   465  // mean that the shell state will be kept, including variables and options.
   466  func (r *Runner) Reset() {
   467  	if !r.usedNew {
   468  		panic("use interp.New to construct a Runner")
   469  	}
   470  	// reset the internal state
   471  	*r = Runner{
   472  		Env:         r.Env,
   473  		Dir:         r.Dir,
   474  		Params:      r.Params,
   475  		Stdin:       r.Stdin,
   476  		Stdout:      r.Stdout,
   477  		Stderr:      r.Stderr,
   478  		Exec:        r.Exec,
   479  		Open:        r.Open,
   480  		KillTimeout: r.KillTimeout,
   481  		opts:        r.opts,
   482  
   483  		// emptied below, to reuse the space
   484  		Vars:     r.Vars,
   485  		cmdVars:  r.cmdVars,
   486  		dirStack: r.dirStack[:0],
   487  		usedNew:  r.usedNew,
   488  	}
   489  	if r.Vars == nil {
   490  		r.Vars = make(map[string]expand.Variable)
   491  	} else {
   492  		for k := range r.Vars {
   493  			delete(r.Vars, k)
   494  		}
   495  	}
   496  	if r.cmdVars == nil {
   497  		r.cmdVars = make(map[string]string)
   498  	} else {
   499  		for k := range r.cmdVars {
   500  			delete(r.cmdVars, k)
   501  		}
   502  	}
   503  	if vr := r.Env.Get("HOME"); !vr.IsSet() {
   504  		u, _ := user.Current()
   505  		r.Vars["HOME"] = expand.Variable{Value: u.HomeDir}
   506  	}
   507  	r.Vars["PWD"] = expand.Variable{Value: r.Dir}
   508  	r.Vars["IFS"] = expand.Variable{Value: " \t\n"}
   509  	r.Vars["OPTIND"] = expand.Variable{Value: "1"}
   510  
   511  	if runtime.GOOS == "windows" {
   512  		// convert $PATH to a unix path list
   513  		path := r.Env.Get("PATH").String()
   514  		path = strings.Join(filepath.SplitList(path), ":")
   515  		r.Vars["PATH"] = expand.Variable{Value: path}
   516  	}
   517  
   518  	r.dirStack = append(r.dirStack, r.Dir)
   519  	if r.KillTimeout == 0 {
   520  		r.KillTimeout = 2 * time.Second
   521  	}
   522  	r.didReset = true
   523  }
   524  
   525  func (r *Runner) modCtx(ctx context.Context) context.Context {
   526  	mc := ModuleCtx{
   527  		Dir:         r.Dir,
   528  		Stdin:       r.Stdin,
   529  		Stdout:      r.Stdout,
   530  		Stderr:      r.Stderr,
   531  		KillTimeout: r.KillTimeout,
   532  	}
   533  	oenv := overlayEnviron{
   534  		parent: r.Env,
   535  		values: make(map[string]expand.Variable),
   536  	}
   537  	for name, vr := range r.Vars {
   538  		oenv.Set(name, vr)
   539  	}
   540  	for name, vr := range r.funcVars {
   541  		oenv.Set(name, vr)
   542  	}
   543  	for name, value := range r.cmdVars {
   544  		oenv.Set(name, expand.Variable{Exported: true, Value: value})
   545  	}
   546  	mc.Env = oenv
   547  	return context.WithValue(ctx, moduleCtxKey{}, mc)
   548  }
   549  
   550  // ShellExitStatus exits the shell with a status code.
   551  type ShellExitStatus uint8
   552  
   553  func (s ShellExitStatus) Error() string { return fmt.Sprintf("exit status %d", s) }
   554  
   555  // ExitStatus is a non-zero status code resulting from running a shell node.
   556  type ExitStatus uint8
   557  
   558  func (s ExitStatus) Error() string { return fmt.Sprintf("exit status %d", s) }
   559  
   560  func (r *Runner) setErr(err error) {
   561  	if r.err == nil {
   562  		r.err = err
   563  	}
   564  }
   565  
   566  // Run interprets a node, which can be a *File, *Stmt, or Command. If a non-nil
   567  // error is returned, it will typically be of type ExitStatus or
   568  // ShellExitStatus.
   569  //
   570  // Run can be called multiple times synchronously to interpret programs
   571  // incrementally. To reuse a Runner without keeping the internal shell state,
   572  // call Reset.
   573  func (r *Runner) Run(ctx context.Context, node syntax.Node) error {
   574  	if !r.didReset {
   575  		r.Reset()
   576  	}
   577  	r.fillExpandConfig(ctx)
   578  	r.err = nil
   579  	r.filename = ""
   580  	switch x := node.(type) {
   581  	case *syntax.File:
   582  		r.filename = x.Name
   583  		r.stmts(ctx, x.StmtList)
   584  	case *syntax.Stmt:
   585  		r.stmt(ctx, x)
   586  	case syntax.Command:
   587  		r.cmd(ctx, x)
   588  	default:
   589  		return fmt.Errorf("node can only be File, Stmt, or Command: %T", x)
   590  	}
   591  	if r.exit > 0 {
   592  		r.setErr(ExitStatus(r.exit))
   593  	}
   594  	return r.err
   595  }
   596  
   597  func (r *Runner) out(s string) {
   598  	io.WriteString(r.Stdout, s)
   599  }
   600  
   601  func (r *Runner) outf(format string, a ...interface{}) {
   602  	fmt.Fprintf(r.Stdout, format, a...)
   603  }
   604  
   605  func (r *Runner) errf(format string, a ...interface{}) {
   606  	fmt.Fprintf(r.Stderr, format, a...)
   607  }
   608  
   609  func (r *Runner) stop(ctx context.Context) bool {
   610  	if r.err != nil {
   611  		return true
   612  	}
   613  	if err := ctx.Err(); err != nil {
   614  		r.err = err
   615  		return true
   616  	}
   617  	if r.opts[optNoExec] {
   618  		return true
   619  	}
   620  	return false
   621  }
   622  
   623  func (r *Runner) stmt(ctx context.Context, st *syntax.Stmt) {
   624  	if r.stop(ctx) {
   625  		return
   626  	}
   627  	if st.Background {
   628  		r2 := r.sub()
   629  		st2 := *st
   630  		st2.Background = false
   631  		r.bgShells.Go(func() error {
   632  			return r2.Run(ctx, &st2)
   633  		})
   634  	} else {
   635  		r.stmtSync(ctx, st)
   636  	}
   637  }
   638  
   639  func (r *Runner) stmtSync(ctx context.Context, st *syntax.Stmt) {
   640  	oldIn, oldOut, oldErr := r.Stdin, r.Stdout, r.Stderr
   641  	for _, rd := range st.Redirs {
   642  		cls, err := r.redir(ctx, rd)
   643  		if err != nil {
   644  			r.exit = 1
   645  			return
   646  		}
   647  		if cls != nil {
   648  			defer cls.Close()
   649  		}
   650  	}
   651  	if st.Cmd == nil {
   652  		r.exit = 0
   653  	} else {
   654  		r.cmd(ctx, st.Cmd)
   655  	}
   656  	if st.Negated {
   657  		r.exit = oneIf(r.exit == 0)
   658  	}
   659  	if r.exit != 0 && r.opts[optErrExit] {
   660  		r.setErr(ShellExitStatus(r.exit))
   661  	}
   662  	if !r.keepRedirs {
   663  		r.Stdin, r.Stdout, r.Stderr = oldIn, oldOut, oldErr
   664  	}
   665  }
   666  
   667  func (r *Runner) sub() *Runner {
   668  	// Keep in sync with the Runner type. Manually copy fields, to not copy
   669  	// sensitive ones like errgroup.Group, and to do deep copies of slices.
   670  	r2 := &Runner{
   671  		Env:         r.Env,
   672  		Dir:         r.Dir,
   673  		Params:      r.Params,
   674  		Exec:        r.Exec,
   675  		Open:        r.Open,
   676  		Stdin:       r.Stdin,
   677  		Stdout:      r.Stdout,
   678  		Stderr:      r.Stderr,
   679  		Funcs:       r.Funcs,
   680  		KillTimeout: r.KillTimeout,
   681  		filename:    r.filename,
   682  		opts:        r.opts,
   683  	}
   684  	r2.Vars = make(map[string]expand.Variable, len(r.Vars))
   685  	for k, v := range r.Vars {
   686  		r2.Vars[k] = v
   687  	}
   688  	r2.funcVars = make(map[string]expand.Variable, len(r.funcVars))
   689  	for k, v := range r.funcVars {
   690  		r2.funcVars[k] = v
   691  	}
   692  	r2.cmdVars = make(map[string]string, len(r.cmdVars))
   693  	for k, v := range r.cmdVars {
   694  		r2.cmdVars[k] = v
   695  	}
   696  	r2.dirStack = append([]string(nil), r.dirStack...)
   697  	r2.fillExpandConfig(r.ectx)
   698  	r2.didReset = true
   699  	return r2
   700  }
   701  
   702  func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
   703  	if r.stop(ctx) {
   704  		return
   705  	}
   706  	switch x := cm.(type) {
   707  	case *syntax.Block:
   708  		r.stmts(ctx, x.StmtList)
   709  	case *syntax.Subshell:
   710  		r2 := r.sub()
   711  		r2.stmts(ctx, x.StmtList)
   712  		r.exit = r2.exit
   713  		r.setErr(r2.err)
   714  	case *syntax.CallExpr:
   715  		fields := r.fields(x.Args...)
   716  		if len(fields) == 0 {
   717  			for _, as := range x.Assigns {
   718  				vr := r.lookupVar(as.Name.Value)
   719  				vr.Value = r.assignVal(as, "")
   720  				r.setVar(as.Name.Value, as.Index, vr)
   721  			}
   722  			break
   723  		}
   724  		for _, as := range x.Assigns {
   725  			val := r.assignVal(as, "")
   726  			// we know that inline vars must be strings
   727  			r.cmdVars[as.Name.Value] = val.(string)
   728  		}
   729  		r.call(ctx, x.Args[0].Pos(), fields)
   730  		// cmdVars can be nuked here, as they are never useful
   731  		// again once we nest into further levels of inline
   732  		// vars.
   733  		for k := range r.cmdVars {
   734  			delete(r.cmdVars, k)
   735  		}
   736  	case *syntax.BinaryCmd:
   737  		switch x.Op {
   738  		case syntax.AndStmt:
   739  			r.stmt(ctx, x.X)
   740  			if r.exit == 0 {
   741  				r.stmt(ctx, x.Y)
   742  			}
   743  		case syntax.OrStmt:
   744  			r.stmt(ctx, x.X)
   745  			if r.exit != 0 {
   746  				r.stmt(ctx, x.Y)
   747  			}
   748  		case syntax.Pipe, syntax.PipeAll:
   749  			pr, pw := io.Pipe()
   750  			r2 := r.sub()
   751  			r2.Stdout = pw
   752  			if x.Op == syntax.PipeAll {
   753  				r2.Stderr = pw
   754  			} else {
   755  				r2.Stderr = r.Stderr
   756  			}
   757  			r.Stdin = pr
   758  			var wg sync.WaitGroup
   759  			wg.Add(1)
   760  			go func() {
   761  				r2.stmt(ctx, x.X)
   762  				pw.Close()
   763  				wg.Done()
   764  			}()
   765  			r.stmt(ctx, x.Y)
   766  			pr.Close()
   767  			wg.Wait()
   768  			if r.opts[optPipeFail] && r2.exit > 0 && r.exit == 0 {
   769  				r.exit = r2.exit
   770  			}
   771  			r.setErr(r2.err)
   772  		}
   773  	case *syntax.IfClause:
   774  		r.stmts(ctx, x.Cond)
   775  		if r.exit == 0 {
   776  			r.stmts(ctx, x.Then)
   777  			break
   778  		}
   779  		r.exit = 0
   780  		r.stmts(ctx, x.Else)
   781  	case *syntax.WhileClause:
   782  		for !r.stop(ctx) {
   783  			r.stmts(ctx, x.Cond)
   784  			stop := (r.exit == 0) == x.Until
   785  			r.exit = 0
   786  			if stop || r.loopStmtsBroken(ctx, x.Do) {
   787  				break
   788  			}
   789  		}
   790  	case *syntax.ForClause:
   791  		switch y := x.Loop.(type) {
   792  		case *syntax.WordIter:
   793  			name := y.Name.Value
   794  			items := r.Params // for i; do ...
   795  			if y.InPos.IsValid() {
   796  				items = r.fields(y.Items...) // for i in ...; do ...
   797  			}
   798  			for _, field := range items {
   799  				r.setVarString(name, field)
   800  				if r.loopStmtsBroken(ctx, x.Do) {
   801  					break
   802  				}
   803  			}
   804  		case *syntax.CStyleLoop:
   805  			r.arithm(y.Init)
   806  			for r.arithm(y.Cond) != 0 {
   807  				if r.loopStmtsBroken(ctx, x.Do) {
   808  					break
   809  				}
   810  				r.arithm(y.Post)
   811  			}
   812  		}
   813  	case *syntax.FuncDecl:
   814  		r.setFunc(x.Name.Value, x.Body)
   815  	case *syntax.ArithmCmd:
   816  		r.exit = oneIf(r.arithm(x.X) == 0)
   817  	case *syntax.LetClause:
   818  		var val int
   819  		for _, expr := range x.Exprs {
   820  			val = r.arithm(expr)
   821  		}
   822  		r.exit = oneIf(val == 0)
   823  	case *syntax.CaseClause:
   824  		str := r.literal(x.Word)
   825  		for _, ci := range x.Items {
   826  			for _, word := range ci.Patterns {
   827  				pattern := r.pattern(word)
   828  				if match(pattern, str) {
   829  					r.stmts(ctx, ci.StmtList)
   830  					return
   831  				}
   832  			}
   833  		}
   834  	case *syntax.TestClause:
   835  		r.exit = 0
   836  		if r.bashTest(ctx, x.X, false) == "" && r.exit == 0 {
   837  			// to preserve exit status code 2 for regex errors, etc
   838  			r.exit = 1
   839  		}
   840  	case *syntax.DeclClause:
   841  		local, global := false, false
   842  		var modes []string
   843  		valType := ""
   844  		switch x.Variant.Value {
   845  		case "declare":
   846  			// When used in a function, "declare" acts as "local"
   847  			// unless the "-g" option is used.
   848  			local = r.inFunc
   849  		case "local":
   850  			if !r.inFunc {
   851  				r.errf("local: can only be used in a function\n")
   852  				r.exit = 1
   853  				return
   854  			}
   855  			local = true
   856  		case "export":
   857  			modes = append(modes, "-x")
   858  		case "readonly":
   859  			modes = append(modes, "-r")
   860  		case "nameref":
   861  			modes = append(modes, "-n")
   862  		}
   863  		for _, opt := range x.Opts {
   864  			switch s := r.literal(opt); s {
   865  			case "-x", "-r", "-n":
   866  				modes = append(modes, s)
   867  			case "-a", "-A":
   868  				valType = s
   869  			case "-g":
   870  				global = true
   871  			default:
   872  				r.errf("declare: invalid option %q\n", s)
   873  				r.exit = 2
   874  				return
   875  			}
   876  		}
   877  		for _, as := range x.Assigns {
   878  			for _, as := range r.flattenAssign(as) {
   879  				name := as.Name.Value
   880  				if !syntax.ValidName(name) {
   881  					r.errf("declare: invalid name %q\n", name)
   882  					r.exit = 1
   883  					return
   884  				}
   885  				vr := r.lookupVar(as.Name.Value)
   886  				vr.Value = r.assignVal(as, valType)
   887  				if global {
   888  					vr.Local = false
   889  				} else if local {
   890  					vr.Local = true
   891  				}
   892  				for _, mode := range modes {
   893  					switch mode {
   894  					case "-x":
   895  						vr.Exported = true
   896  					case "-r":
   897  						vr.ReadOnly = true
   898  					case "-n":
   899  						vr.NameRef = true
   900  					}
   901  				}
   902  				r.setVar(name, as.Index, vr)
   903  			}
   904  		}
   905  	case *syntax.TimeClause:
   906  		start := time.Now()
   907  		if x.Stmt != nil {
   908  			r.stmt(ctx, x.Stmt)
   909  		}
   910  		format := "%s\t%s\n"
   911  		if x.PosixFormat {
   912  			format = "%s %s\n"
   913  		} else {
   914  			r.outf("\n")
   915  		}
   916  		real := time.Since(start)
   917  		r.outf(format, "real", elapsedString(real, x.PosixFormat))
   918  		// TODO: can we do these?
   919  		r.outf(format, "user", elapsedString(0, x.PosixFormat))
   920  		r.outf(format, "sys", elapsedString(0, x.PosixFormat))
   921  	default:
   922  		panic(fmt.Sprintf("unhandled command node: %T", x))
   923  	}
   924  }
   925  
   926  func (r *Runner) flattenAssign(as *syntax.Assign) []*syntax.Assign {
   927  	// Convert "declare $x" into "declare value".
   928  	// Don't use syntax.Parser here, as we only want the basic
   929  	// splitting by '='.
   930  	if as.Name != nil {
   931  		return []*syntax.Assign{as} // nothing to do
   932  	}
   933  	var asgns []*syntax.Assign
   934  	for _, field := range r.fields(as.Value) {
   935  		as := &syntax.Assign{}
   936  		parts := strings.SplitN(field, "=", 2)
   937  		as.Name = &syntax.Lit{Value: parts[0]}
   938  		if len(parts) == 1 {
   939  			as.Naked = true
   940  		} else {
   941  			as.Value = &syntax.Word{Parts: []syntax.WordPart{
   942  				&syntax.Lit{Value: parts[1]},
   943  			}}
   944  		}
   945  		asgns = append(asgns, as)
   946  	}
   947  	return asgns
   948  }
   949  
   950  func match(pattern, name string) bool {
   951  	expr, err := syntax.TranslatePattern(pattern, true)
   952  	if err != nil {
   953  		return false
   954  	}
   955  	rx := regexp.MustCompile("^" + expr + "$")
   956  	return rx.MatchString(name)
   957  }
   958  
   959  func elapsedString(d time.Duration, posix bool) string {
   960  	if posix {
   961  		return fmt.Sprintf("%.2f", d.Seconds())
   962  	}
   963  	min := int(d.Minutes())
   964  	sec := math.Remainder(d.Seconds(), 60.0)
   965  	return fmt.Sprintf("%dm%.3fs", min, sec)
   966  }
   967  
   968  func (r *Runner) stmts(ctx context.Context, sl syntax.StmtList) {
   969  	for _, stmt := range sl.Stmts {
   970  		r.stmt(ctx, stmt)
   971  	}
   972  }
   973  
   974  func (r *Runner) hdocReader(rd *syntax.Redirect) io.Reader {
   975  	if rd.Op != syntax.DashHdoc {
   976  		hdoc := r.document(rd.Hdoc)
   977  		return strings.NewReader(hdoc)
   978  	}
   979  	var buf bytes.Buffer
   980  	var cur []syntax.WordPart
   981  	flushLine := func() {
   982  		if buf.Len() > 0 {
   983  			buf.WriteByte('\n')
   984  		}
   985  		buf.WriteString(r.document(&syntax.Word{Parts: cur}))
   986  		cur = cur[:0]
   987  	}
   988  	for _, wp := range rd.Hdoc.Parts {
   989  		lit, ok := wp.(*syntax.Lit)
   990  		if !ok {
   991  			cur = append(cur, wp)
   992  			continue
   993  		}
   994  		for i, part := range strings.Split(lit.Value, "\n") {
   995  			if i > 0 {
   996  				flushLine()
   997  				cur = cur[:0]
   998  			}
   999  			part = strings.TrimLeft(part, "\t")
  1000  			cur = append(cur, &syntax.Lit{Value: part})
  1001  		}
  1002  	}
  1003  	flushLine()
  1004  	return &buf
  1005  }
  1006  
  1007  func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, error) {
  1008  	if rd.Hdoc != nil {
  1009  		r.Stdin = r.hdocReader(rd)
  1010  		return nil, nil
  1011  	}
  1012  	orig := &r.Stdout
  1013  	if rd.N != nil {
  1014  		switch rd.N.Value {
  1015  		case "1":
  1016  		case "2":
  1017  			orig = &r.Stderr
  1018  		}
  1019  	}
  1020  	arg := r.literal(rd.Word)
  1021  	switch rd.Op {
  1022  	case syntax.WordHdoc:
  1023  		r.Stdin = strings.NewReader(arg + "\n")
  1024  		return nil, nil
  1025  	case syntax.DplOut:
  1026  		switch arg {
  1027  		case "1":
  1028  			*orig = r.Stdout
  1029  		case "2":
  1030  			*orig = r.Stderr
  1031  		}
  1032  		return nil, nil
  1033  	case syntax.RdrIn, syntax.RdrOut, syntax.AppOut,
  1034  		syntax.RdrAll, syntax.AppAll:
  1035  		// done further below
  1036  	// case syntax.DplIn:
  1037  	default:
  1038  		panic(fmt.Sprintf("unhandled redirect op: %v", rd.Op))
  1039  	}
  1040  	mode := os.O_RDONLY
  1041  	switch rd.Op {
  1042  	case syntax.AppOut, syntax.AppAll:
  1043  		mode = os.O_WRONLY | os.O_CREATE | os.O_APPEND
  1044  	case syntax.RdrOut, syntax.RdrAll:
  1045  		mode = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
  1046  	}
  1047  	f, err := r.open(ctx, r.relPath(arg), mode, 0644, true)
  1048  	if err != nil {
  1049  		return nil, err
  1050  	}
  1051  	switch rd.Op {
  1052  	case syntax.RdrIn:
  1053  		r.Stdin = f
  1054  	case syntax.RdrOut, syntax.AppOut:
  1055  		*orig = f
  1056  	case syntax.RdrAll, syntax.AppAll:
  1057  		r.Stdout = f
  1058  		r.Stderr = f
  1059  	default:
  1060  		panic(fmt.Sprintf("unhandled redirect op: %v", rd.Op))
  1061  	}
  1062  	return f, nil
  1063  }
  1064  
  1065  func (r *Runner) loopStmtsBroken(ctx context.Context, sl syntax.StmtList) bool {
  1066  	oldInLoop := r.inLoop
  1067  	r.inLoop = true
  1068  	defer func() { r.inLoop = oldInLoop }()
  1069  	for _, stmt := range sl.Stmts {
  1070  		r.stmt(ctx, stmt)
  1071  		if r.contnEnclosing > 0 {
  1072  			r.contnEnclosing--
  1073  			return r.contnEnclosing > 0
  1074  		}
  1075  		if r.breakEnclosing > 0 {
  1076  			r.breakEnclosing--
  1077  			return true
  1078  		}
  1079  	}
  1080  	return false
  1081  }
  1082  
  1083  type returnStatus uint8
  1084  
  1085  func (s returnStatus) Error() string { return fmt.Sprintf("return status %d", s) }
  1086  
  1087  func (r *Runner) call(ctx context.Context, pos syntax.Pos, args []string) {
  1088  	if r.stop(ctx) {
  1089  		return
  1090  	}
  1091  	name := args[0]
  1092  	if body := r.Funcs[name]; body != nil {
  1093  		// stack them to support nested func calls
  1094  		oldParams := r.Params
  1095  		r.Params = args[1:]
  1096  		oldInFunc := r.inFunc
  1097  		oldFuncVars := r.funcVars
  1098  		r.funcVars = nil
  1099  		r.inFunc = true
  1100  
  1101  		r.stmt(ctx, body)
  1102  
  1103  		r.Params = oldParams
  1104  		r.funcVars = oldFuncVars
  1105  		r.inFunc = oldInFunc
  1106  		if code, ok := r.err.(returnStatus); ok {
  1107  			r.err = nil
  1108  			r.exit = int(code)
  1109  		}
  1110  		return
  1111  	}
  1112  	if isBuiltin(name) {
  1113  		r.exit = r.builtinCode(ctx, pos, name, args[1:])
  1114  		return
  1115  	}
  1116  	r.exec(ctx, args)
  1117  }
  1118  
  1119  func (r *Runner) exec(ctx context.Context, args []string) {
  1120  	path := r.lookPath(args[0])
  1121  	err := r.Exec(r.modCtx(ctx), path, args)
  1122  	switch x := err.(type) {
  1123  	case nil:
  1124  		r.exit = 0
  1125  	case ExitStatus:
  1126  		r.exit = int(x)
  1127  	default: // module's custom fatal error
  1128  		r.setErr(err)
  1129  	}
  1130  }
  1131  
  1132  func (r *Runner) open(ctx context.Context, path string, flags int, mode os.FileMode, print bool) (io.ReadWriteCloser, error) {
  1133  	f, err := r.Open(r.modCtx(ctx), path, flags, mode)
  1134  	switch err.(type) {
  1135  	case nil:
  1136  	case *os.PathError:
  1137  		if print {
  1138  			r.errf("%v\n", err)
  1139  		}
  1140  	default: // module's custom fatal error
  1141  		r.setErr(err)
  1142  	}
  1143  	return f, err
  1144  }
  1145  
  1146  func (r *Runner) stat(name string) (os.FileInfo, error) {
  1147  	return os.Stat(r.relPath(name))
  1148  }
  1149  
  1150  func (r *Runner) checkStat(file string) string {
  1151  	d, err := r.stat(file)
  1152  	if err != nil {
  1153  		return ""
  1154  	}
  1155  	m := d.Mode()
  1156  	if m.IsDir() {
  1157  		return ""
  1158  	}
  1159  	if runtime.GOOS != "windows" && m&0111 == 0 {
  1160  		return ""
  1161  	}
  1162  	return file
  1163  }
  1164  
  1165  func winHasExt(file string) bool {
  1166  	i := strings.LastIndex(file, ".")
  1167  	if i < 0 {
  1168  		return false
  1169  	}
  1170  	return strings.LastIndexAny(file, `:\/`) < i
  1171  }
  1172  
  1173  func (r *Runner) findExecutable(file string, exts []string) string {
  1174  	if len(exts) == 0 {
  1175  		// non-windows
  1176  		return r.checkStat(file)
  1177  	}
  1178  	if winHasExt(file) && r.checkStat(file) != "" {
  1179  		return file
  1180  	}
  1181  	for _, e := range exts {
  1182  		if f := file + e; r.checkStat(f) != "" {
  1183  			return f
  1184  		}
  1185  	}
  1186  	return ""
  1187  }
  1188  
  1189  func driveLetter(c byte) bool {
  1190  	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
  1191  }
  1192  
  1193  // splitList is like filepath.SplitList, but always using the unix path
  1194  // list separator ':'. On Windows, it also makes sure not to split
  1195  // [A-Z]:[/\].
  1196  func splitList(path string) []string {
  1197  	if path == "" {
  1198  		return []string{""}
  1199  	}
  1200  	list := strings.Split(path, ":")
  1201  	if runtime.GOOS != "windows" {
  1202  		return list
  1203  	}
  1204  	// join "C", "/foo" into "C:/foo"
  1205  	var fixed []string
  1206  	for i := 0; i < len(list); i++ {
  1207  		s := list[i]
  1208  		switch {
  1209  		case len(s) != 1, !driveLetter(s[0]):
  1210  		case i+1 >= len(list):
  1211  			// last element
  1212  		case strings.IndexAny(list[i+1], `/\`) != 0:
  1213  			// next element doesn't start with / or \
  1214  		default:
  1215  			fixed = append(fixed, s+":"+list[i+1])
  1216  			i++
  1217  			continue
  1218  		}
  1219  		fixed = append(fixed, s)
  1220  	}
  1221  	return fixed
  1222  }
  1223  
  1224  func (r *Runner) lookPath(file string) string {
  1225  	pathList := splitList(r.envGet("PATH"))
  1226  	chars := `/`
  1227  	if runtime.GOOS == "windows" {
  1228  		chars = `:\/`
  1229  		// so that "foo" always tries "./foo"
  1230  		pathList = append([]string{"."}, pathList...)
  1231  	}
  1232  	exts := r.pathExts()
  1233  	if strings.ContainsAny(file, chars) {
  1234  		return r.findExecutable(file, exts)
  1235  	}
  1236  	for _, dir := range pathList {
  1237  		var path string
  1238  		switch dir {
  1239  		case "", ".":
  1240  			// otherwise "foo" won't be "./foo"
  1241  			path = "." + string(filepath.Separator) + file
  1242  		default:
  1243  			path = filepath.Join(dir, file)
  1244  		}
  1245  		if f := r.findExecutable(path, exts); f != "" {
  1246  			return f
  1247  		}
  1248  	}
  1249  	return ""
  1250  }
  1251  
  1252  func (r *Runner) pathExts() []string {
  1253  	if runtime.GOOS != "windows" {
  1254  		return nil
  1255  	}
  1256  	pathext := r.envGet("PATHEXT")
  1257  	if pathext == "" {
  1258  		return []string{".com", ".exe", ".bat", ".cmd"}
  1259  	}
  1260  	var exts []string
  1261  	for _, e := range strings.Split(strings.ToLower(pathext), `;`) {
  1262  		if e == "" {
  1263  			continue
  1264  		}
  1265  		if e[0] != '.' {
  1266  			e = "." + e
  1267  		}
  1268  		exts = append(exts, e)
  1269  	}
  1270  	return exts
  1271  }