1// Copyright (c) 2017, Daniel MartΓ <mvdan@mvdan.cc>
2// See LICENSE for licensing information
3
4// Package interp implements an interpreter that executes shell
5// programs. It aims to support POSIX, but its support is not complete
6// yet. It also supports some Bash features.
7//
8// The interpreter generally aims to behave like Bash,
9// but it does not support all of its features.
10//
11// The interpreter currently aims to behave like a non-interactive shell,
12// which is how most shells run scripts, and is more useful to machines.
13// In the future, it may gain an option to behave like an interactive shell.
14package interp
15
16import (
17 "context"
18 "errors"
19 "fmt"
20 "io"
21 "io/fs"
22 "maps"
23 "math/rand"
24 "os"
25 "path/filepath"
26 "slices"
27 "strconv"
28 "sync"
29 "time"
30
31 "mvdan.cc/sh/v3/expand"
32 "mvdan.cc/sh/v3/syntax"
33)
34
35// A Runner interprets shell programs. It can be reused, but it is not safe for
36// concurrent use. Use [New] to build a new Runner.
37//
38// Note that writes to Stdout and Stderr may be concurrent if background
39// commands are used. If you plan on using an [io.Writer] implementation that
40// isn't safe for concurrent use, consider a workaround like hiding writes
41// behind a mutex.
42//
43// Runner's exported fields are meant to be configured via [RunnerOption];
44// once a Runner has been created, the fields should be treated as read-only.
45type Runner struct {
46 // Env specifies the initial environment for the interpreter, which must
47 // not be nil. It can only be set via [Env].
48 //
49 // If it includes a TMPDIR variable describing an absolute directory,
50 // it is used as the directory in which to create temporary files needed
51 // for the interpreter's use, such as named pipes for process substitutions.
52 // Otherwise, [os.TempDir] is used.
53 Env expand.Environ
54
55 writeEnv expand.WriteEnviron
56
57 // Dir specifies the working directory of the command, which must be an
58 // absolute path. It can only be set via [Dir].
59 Dir string
60
61 // tempDir is either $TMPDIR from [Runner.Env], or [os.TempDir].
62 tempDir string
63
64 // Params are the current shell parameters, e.g. from running a shell
65 // file or calling a function. Accessible via the $@/$* family of vars.
66 // It can only be set via [Params].
67 Params []string
68
69 // Separate maps - note that bash allows a name to be both a var and a
70 // func simultaneously.
71 // Vars is mostly superseded by Env at this point.
72 // TODO(v4): remove these
73
74 Vars map[string]expand.Variable
75 Funcs map[string]*syntax.Stmt
76
77 alias map[string]alias
78
79 // callHandler is a function allowing to replace a simple command's
80 // arguments. It may be nil.
81 callHandler CallHandlerFunc
82
83 // execHandler is responsible for executing programs. It must not be nil.
84 execHandler ExecHandlerFunc
85
86 // execMiddlewares grows with calls to [ExecHandlers],
87 // and is used to construct execHandler when Reset is first called.
88 // The slice is needed to preserve the relative order of middlewares.
89 execMiddlewares []func(ExecHandlerFunc) ExecHandlerFunc
90
91 // openHandler is a function responsible for opening files. It must not be nil.
92 openHandler OpenHandlerFunc
93
94 // readDirHandler is a function responsible for reading directories during
95 // glob expansion. It must be non-nil.
96 readDirHandler ReadDirHandlerFunc2
97
98 // statHandler is a function responsible for getting file stat. It must be non-nil.
99 statHandler StatHandlerFunc
100
101 stdin *os.File // e.g. the read end of a pipe
102 stdout io.Writer
103 stderr io.Writer
104
105 ecfg *expand.Config
106 ectx context.Context // just so that Runner.Subshell can use it again
107
108 lastExpandExit int // used to surface exit codes while expanding fields
109
110 // didReset remembers whether the runner has ever been reset. This is
111 // used so that Reset is automatically called when running any program
112 // or node for the first time on a Runner.
113 didReset bool
114
115 usedNew bool
116
117 // rand is used mainly to generate temporary files.
118 rand *rand.Rand
119
120 filename string // only if Node was a File
121
122 // >0 to break or continue out of N enclosing loops
123 breakEnclosing, contnEnclosing int
124
125 inLoop bool
126 inFunc bool
127 inSource bool
128 noErrExit bool
129
130 // track if a sourced script set positional parameters
131 sourceSetParams bool
132
133 err error // current shell exit code or fatal error
134 handlingTrap bool // whether we're currently in a trap callback
135 shellExited bool // whether the shell needs to exit
136
137 // The current and last exit status code. They can only be different if
138 // the interpreter is in the middle of running a statement. In that
139 // scenario, 'exit' is the status code for the statement being run, and
140 // 'lastExit' corresponds to the previous statement that was run.
141 exit int
142 lastExit int
143
144 bgShells sync.WaitGroup
145
146 opts runnerOpts
147
148 origDir string
149 origParams []string
150 origOpts runnerOpts
151 origStdin *os.File
152 origStdout io.Writer
153 origStderr io.Writer
154
155 // Most scripts don't use pushd/popd, so make space for the initial PWD
156 // without requiring an extra allocation.
157 dirStack []string
158 dirBootstrap [1]string
159
160 optState getopts
161
162 // keepRedirs is used so that "exec" can make any redirections
163 // apply to the current shell, and not just the command.
164 keepRedirs bool
165
166 // Fake signal callbacks
167 callbackErr string
168 callbackExit string
169}
170
171type alias struct {
172 args []*syntax.Word
173 blank bool
174}
175
176func (r *Runner) optByFlag(flag byte) *bool {
177 for i, opt := range &shellOptsTable {
178 if opt.flag == flag {
179 return &r.opts[i]
180 }
181 }
182 return nil
183}
184
185// New creates a new Runner, applying a number of options. If applying any of
186// the options results in an error, it is returned.
187//
188// Any unset options fall back to their defaults. For example, not supplying the
189// environment falls back to the process's environment, and not supplying the
190// standard output writer means that the output will be discarded.
191func New(opts ...RunnerOption) (*Runner, error) {
192 r := &Runner{
193 usedNew: true,
194 openHandler: DefaultOpenHandler(),
195 readDirHandler: DefaultReadDirHandler2(),
196 statHandler: DefaultStatHandler(),
197 }
198 r.dirStack = r.dirBootstrap[:0]
199 // turn "on" the default Bash options
200 for i, opt := range bashOptsTable {
201 r.opts[len(shellOptsTable)+i] = opt.defaultState
202 }
203
204 for _, opt := range opts {
205 if err := opt(r); err != nil {
206 return nil, err
207 }
208 }
209
210 // Set the default fallbacks, if necessary.
211 if r.Env == nil {
212 Env(nil)(r)
213 }
214 if r.Dir == "" {
215 if err := Dir("")(r); err != nil {
216 return nil, err
217 }
218 }
219 if r.stdout == nil || r.stderr == nil {
220 StdIO(r.stdin, r.stdout, r.stderr)(r)
221 }
222 return r, nil
223}
224
225// RunnerOption can be passed to [New] to alter a [Runner]'s behaviour.
226// It can also be applied directly on an existing Runner,
227// such as interp.Params("-e")(runner).
228// Note that options cannot be applied once Run or Reset have been called.
229type RunnerOption func(*Runner) error
230
231// TODO: enforce the rule above via didReset.
232
233// Env sets the interpreter's environment. If nil, a copy of the current
234// process's environment is used.
235func Env(env expand.Environ) RunnerOption {
236 return func(r *Runner) error {
237 if env == nil {
238 env = expand.ListEnviron(os.Environ()...)
239 }
240 r.Env = env
241 return nil
242 }
243}
244
245// Dir sets the interpreter's working directory. If empty, the process's current
246// directory is used.
247func Dir(path string) RunnerOption {
248 return func(r *Runner) error {
249 if path == "" {
250 path, err := os.Getwd()
251 if err != nil {
252 return fmt.Errorf("could not get current dir: %w", err)
253 }
254 r.Dir = path
255 return nil
256 }
257 path, err := filepath.Abs(path)
258 if err != nil {
259 return fmt.Errorf("could not get absolute dir: %w", err)
260 }
261 info, err := os.Stat(path)
262 if err != nil {
263 return fmt.Errorf("could not stat: %w", err)
264 }
265 if !info.IsDir() {
266 return fmt.Errorf("%s is not a directory", path)
267 }
268 r.Dir = path
269 return nil
270 }
271}
272
273// Interactive configures the interpreter to behave like an interactive shell,
274// akin to Bash. Currently, this only enables the expansion of aliases,
275// but later on it should also change other behavior.
276func Interactive(enabled bool) RunnerOption {
277 return func(r *Runner) error {
278 r.opts[optExpandAliases] = enabled
279 return nil
280 }
281}
282
283// Params populates the shell options and parameters. For example, Params("-e",
284// "--", "foo") will set the "-e" option and the parameters ["foo"], and
285// Params("+e") will unset the "-e" option and leave the parameters untouched.
286//
287// This is similar to what the interpreter's "set" builtin does.
288func Params(args ...string) RunnerOption {
289 return func(r *Runner) error {
290 fp := flagParser{remaining: args}
291 for fp.more() {
292 flag := fp.flag()
293 if flag == "-" {
294 // TODO: implement "The -x and -v options are turned off."
295 if args := fp.args(); len(args) > 0 {
296 r.Params = args
297 }
298 return nil
299 }
300 enable := flag[0] == '-'
301 if flag[1] != 'o' {
302 opt := r.optByFlag(flag[1])
303 if opt == nil {
304 return fmt.Errorf("invalid option: %q", flag)
305 }
306 *opt = enable
307 continue
308 }
309 value := fp.value()
310 if value == "" && enable {
311 for i, opt := range &shellOptsTable {
312 r.printOptLine(opt.name, r.opts[i], true)
313 }
314 continue
315 }
316 if value == "" && !enable {
317 for i, opt := range &shellOptsTable {
318 setFlag := "+o"
319 if r.opts[i] {
320 setFlag = "-o"
321 }
322 r.outf("set %s %s\n", setFlag, opt.name)
323 }
324 continue
325 }
326 _, opt := r.optByName(value, false)
327 if opt == nil {
328 return fmt.Errorf("invalid option: %q", value)
329 }
330 *opt = enable
331 }
332 if args := fp.args(); args != nil {
333 // If "--" wasn't given and there were zero arguments,
334 // we don't want to override the current parameters.
335 r.Params = args
336
337 // Record whether a sourced script sets the parameters.
338 if r.inSource {
339 r.sourceSetParams = true
340 }
341 }
342 return nil
343 }
344}
345
346// CallHandler sets the call handler. See [CallHandlerFunc] for more info.
347func CallHandler(f CallHandlerFunc) RunnerOption {
348 return func(r *Runner) error {
349 r.callHandler = f
350 return nil
351 }
352}
353
354// ExecHandler sets one command execution handler,
355// which replaces DefaultExecHandler(2 * time.Second).
356//
357// Deprecated: use [ExecHandlers] instead, which allows for middleware handlers.
358func ExecHandler(f ExecHandlerFunc) RunnerOption {
359 return func(r *Runner) error {
360 r.execHandler = f
361 return nil
362 }
363}
364
365// ExecHandlers appends middlewares to handle command execution.
366// The middlewares are chained from first to last, and the first is called by the runner.
367// Each middleware is expected to call the "next" middleware at most once.
368//
369// For example, a middleware may implement only some commands.
370// For those commands, it can run its logic and avoid calling "next".
371// For any other commands, it can call "next" with the original parameters.
372//
373// Another common example is a middleware which always calls "next",
374// but runs custom logic either before or after that call.
375// For instance, a middleware could change the arguments to the "next" call,
376// or it could print log lines before or after the call to "next".
377//
378// The last exec handler is DefaultExecHandler(2 * time.Second).
379func ExecHandlers(middlewares ...func(next ExecHandlerFunc) ExecHandlerFunc) RunnerOption {
380 return func(r *Runner) error {
381 r.execMiddlewares = append(r.execMiddlewares, middlewares...)
382 return nil
383 }
384}
385
386// TODO: consider porting the middleware API in [ExecHandlers] to [OpenHandler],
387// ReadDirHandler, and StatHandler.
388
389// TODO(v4): now that [ExecHandlers] allows calling a next handler with changed
390// arguments, one of the two advantages of [CallHandler] is gone. The other is the
391// ability to work with builtins; if we make [ExecHandlers] work with builtins, we
392// could join both APIs.
393
394// OpenHandler sets file open handler. See [OpenHandlerFunc] for more info.
395func OpenHandler(f OpenHandlerFunc) RunnerOption {
396 return func(r *Runner) error {
397 r.openHandler = f
398 return nil
399 }
400}
401
402// ReadDirHandler sets the read directory handler. See [ReadDirHandlerFunc] for more info.
403//
404// Deprecated: use [ReadDirHandler2].
405func ReadDirHandler(f ReadDirHandlerFunc) RunnerOption {
406 return func(r *Runner) error {
407 r.readDirHandler = func(ctx context.Context, path string) ([]fs.DirEntry, error) {
408 infos, err := f(ctx, path)
409 if err != nil {
410 return nil, err
411 }
412 entries := make([]fs.DirEntry, len(infos))
413 for i, info := range infos {
414 entries[i] = fs.FileInfoToDirEntry(info)
415 }
416 return entries, nil
417 }
418 return nil
419 }
420}
421
422// ReadDirHandler2 sets the read directory handler. See [ReadDirHandlerFunc2] for more info.
423func ReadDirHandler2(f ReadDirHandlerFunc2) RunnerOption {
424 return func(r *Runner) error {
425 r.readDirHandler = f
426 return nil
427 }
428}
429
430// StatHandler sets the stat handler. See [StatHandlerFunc] for more info.
431func StatHandler(f StatHandlerFunc) RunnerOption {
432 return func(r *Runner) error {
433 r.statHandler = f
434 return nil
435 }
436}
437
438func stdinFile(r io.Reader) (*os.File, error) {
439 switch r := r.(type) {
440 case *os.File:
441 return r, nil
442 case nil:
443 return nil, nil
444 default:
445 pr, pw, err := os.Pipe()
446 if err != nil {
447 return nil, err
448 }
449 go func() {
450 io.Copy(pw, r)
451 pw.Close()
452 }()
453 return pr, nil
454 }
455}
456
457// StdIO configures an interpreter's standard input, standard output, and
458// standard error. If out or err are nil, they default to a writer that discards
459// the output.
460//
461// Note that providing a non-nil standard input other than [*os.File] will require
462// an [os.Pipe] and spawning a goroutine to copy into it,
463// as an [os.File] is the only way to share a reader with subprocesses.
464// This may cause the interpreter to consume the entire reader.
465// See [os/exec.Cmd.Stdin].
466//
467// When providing an [*os.File] as standard input, consider using an [os.Pipe]
468// as it has the best chance to support cancellable reads via [os.File.SetReadDeadline],
469// so that cancelling the runner's context can stop a blocked standard input read.
470func StdIO(in io.Reader, out, err io.Writer) RunnerOption {
471 return func(r *Runner) error {
472 stdin, _err := stdinFile(in)
473 if _err != nil {
474 return _err
475 }
476 r.stdin = stdin
477 if out == nil {
478 out = io.Discard
479 }
480 r.stdout = out
481 if err == nil {
482 err = io.Discard
483 }
484 r.stderr = err
485 return nil
486 }
487}
488
489// optByName returns the matching runner's option index and status
490func (r *Runner) optByName(name string, bash bool) (index int, status *bool) {
491 if bash {
492 for i, opt := range bashOptsTable {
493 if opt.name == name {
494 index = len(shellOptsTable) + i
495 return index, &r.opts[index]
496 }
497 }
498 }
499 for i, opt := range &shellOptsTable {
500 if opt.name == name {
501 return i, &r.opts[i]
502 }
503 }
504 return 0, nil
505}
506
507type runnerOpts [len(shellOptsTable) + len(bashOptsTable)]bool
508
509type shellOpt struct {
510 flag byte
511 name string
512}
513
514type bashOpt struct {
515 name string
516 defaultState bool // Bash's default value for this option
517 supported bool // whether we support the option's non-default state
518}
519
520var shellOptsTable = [...]shellOpt{
521 // sorted alphabetically by name; use a space for the options
522 // that have no flag form
523 {'a', "allexport"},
524 {'e', "errexit"},
525 {'n', "noexec"},
526 {'f', "noglob"},
527 {'u', "nounset"},
528 {'x', "xtrace"},
529 {' ', "pipefail"},
530}
531
532var bashOptsTable = [...]bashOpt{
533 // supported options, sorted alphabetically by name
534 {
535 name: "expand_aliases",
536 defaultState: false,
537 supported: true,
538 },
539 {
540 name: "globstar",
541 defaultState: false,
542 supported: true,
543 },
544 {
545 name: "nocaseglob",
546 defaultState: false,
547 supported: true,
548 },
549 {
550 name: "nullglob",
551 defaultState: false,
552 supported: true,
553 },
554 // unsupported options, sorted alphabetically by name
555 {name: "assoc_expand_once"},
556 {name: "autocd"},
557 {name: "cdable_vars"},
558 {name: "cdspell"},
559 {name: "checkhash"},
560 {name: "checkjobs"},
561 {
562 name: "checkwinsize",
563 defaultState: true,
564 },
565 {
566 name: "cmdhist",
567 defaultState: true,
568 },
569 {name: "compat31"},
570 {name: "compat32"},
571 {name: "compat40"},
572 {name: "compat41"},
573 {name: "compat42"},
574 {name: "compat44"},
575 {name: "compat43"},
576 {name: "compat44"},
577 {
578 name: "complete_fullquote",
579 defaultState: true,
580 },
581 {name: "direxpand"},
582 {name: "dirspell"},
583 {name: "dotglob"},
584 {name: "execfail"},
585 {name: "extdebug"},
586 {name: "extglob"},
587 {
588 name: "extquote",
589 defaultState: true,
590 },
591 {name: "failglob"},
592 {
593 name: "force_fignore",
594 defaultState: true,
595 },
596 {name: "globasciiranges"},
597 {name: "gnu_errfmt"},
598 {name: "histappend"},
599 {name: "histreedit"},
600 {name: "histverify"},
601 {
602 name: "hostcomplete",
603 defaultState: true,
604 },
605 {name: "huponexit"},
606 {
607 name: "inherit_errexit",
608 defaultState: true,
609 },
610 {
611 name: "interactive_comments",
612 defaultState: true,
613 },
614 {name: "lastpipe"},
615 {name: "lithist"},
616 {name: "localvar_inherit"},
617 {name: "localvar_unset"},
618 {name: "login_shell"},
619 {name: "mailwarn"},
620 {name: "no_empty_cmd_completion"},
621 {name: "nocasematch"},
622 {
623 name: "progcomp",
624 defaultState: true,
625 },
626 {name: "progcomp_alias"},
627 {
628 name: "promptvars",
629 defaultState: true,
630 },
631 {name: "restricted_shell"},
632 {name: "shift_verbose"},
633 {
634 name: "sourcepath",
635 defaultState: true,
636 },
637 {name: "xpg_echo"},
638}
639
640// To access the shell options arrays without a linear search when we
641// know which option we're after at compile time. First come the shell options,
642// then the bash options.
643const (
644 // These correspond to indexes in [shellOptsTable]
645 optAllExport = iota
646 optErrExit
647 optNoExec
648 optNoGlob
649 optNoUnset
650 optXTrace
651 optPipeFail
652
653 // These correspond to indexes (offset by the above seven items) of
654 // supported options in [bashOptsTable]
655 optExpandAliases
656 optGlobStar
657 optNoCaseGlob
658 optNullGlob
659)
660
661// Reset returns a runner to its initial state, right before the first call to
662// Run or Reset.
663//
664// Typically, this function only needs to be called if a runner is reused to run
665// multiple programs non-incrementally. Not calling Reset between each run will
666// mean that the shell state will be kept, including variables, options, and the
667// current directory.
668func (r *Runner) Reset() {
669 if !r.usedNew {
670 panic("use interp.New to construct a Runner")
671 }
672 if !r.didReset {
673 r.origDir = r.Dir
674 r.origParams = r.Params
675 r.origOpts = r.opts
676 r.origStdin = r.stdin
677 r.origStdout = r.stdout
678 r.origStderr = r.stderr
679
680 if r.execHandler != nil && len(r.execMiddlewares) > 0 {
681 panic("interp.ExecHandler should be replaced with interp.ExecHandlers, not mixed")
682 }
683 if r.execHandler == nil {
684 r.execHandler = DefaultExecHandler(2 * time.Second)
685 }
686 // Middlewares are chained from first to last, and each can call the
687 // next in the chain, so we need to construct the chain backwards.
688 for _, mw := range slices.Backward(r.execMiddlewares) {
689 r.execHandler = mw(r.execHandler)
690 }
691 // Fill tempDir; only need to do this once given that Env will not change.
692 if dir := r.Env.Get("TMPDIR").String(); filepath.IsAbs(dir) {
693 r.tempDir = dir
694 } else {
695 r.tempDir = os.TempDir()
696 }
697 // Clean it as we will later do a string prefix match.
698 r.tempDir = filepath.Clean(r.tempDir)
699 }
700 // reset the internal state
701 *r = Runner{
702 Env: r.Env,
703 tempDir: r.tempDir,
704 callHandler: r.callHandler,
705 execHandler: r.execHandler,
706 openHandler: r.openHandler,
707 readDirHandler: r.readDirHandler,
708 statHandler: r.statHandler,
709
710 // These can be set by functions like [Dir] or [Params], but
711 // builtins can overwrite them; reset the fields to whatever the
712 // constructor set up.
713 Dir: r.origDir,
714 Params: r.origParams,
715 opts: r.origOpts,
716 stdin: r.origStdin,
717 stdout: r.origStdout,
718 stderr: r.origStderr,
719
720 origDir: r.origDir,
721 origParams: r.origParams,
722 origOpts: r.origOpts,
723 origStdin: r.origStdin,
724 origStdout: r.origStdout,
725 origStderr: r.origStderr,
726
727 // emptied below, to reuse the space
728 Vars: r.Vars,
729 dirStack: r.dirStack[:0],
730 usedNew: r.usedNew,
731 }
732 if r.Vars == nil {
733 r.Vars = make(map[string]expand.Variable)
734 } else {
735 clear(r.Vars)
736 }
737 // TODO(v4): Use the supplied Env directly if it implements enough methods.
738 r.writeEnv = &overlayEnviron{parent: r.Env}
739 if !r.writeEnv.Get("HOME").IsSet() {
740 home, _ := os.UserHomeDir()
741 r.setVarString("HOME", home)
742 }
743 if !r.writeEnv.Get("UID").IsSet() {
744 r.setVar("UID", expand.Variable{
745 Set: true,
746 Kind: expand.String,
747 ReadOnly: true,
748 Str: strconv.Itoa(os.Getuid()),
749 })
750 }
751 if !r.writeEnv.Get("EUID").IsSet() {
752 r.setVar("EUID", expand.Variable{
753 Set: true,
754 Kind: expand.String,
755 ReadOnly: true,
756 Str: strconv.Itoa(os.Geteuid()),
757 })
758 }
759 if !r.writeEnv.Get("GID").IsSet() {
760 r.setVar("GID", expand.Variable{
761 Set: true,
762 Kind: expand.String,
763 ReadOnly: true,
764 Str: strconv.Itoa(os.Getgid()),
765 })
766 }
767 r.setVarString("PWD", r.Dir)
768 r.setVarString("IFS", " \t\n")
769 r.setVarString("OPTIND", "1")
770
771 r.dirStack = append(r.dirStack, r.Dir)
772
773 r.didReset = true
774}
775
776// exitStatus is a non-zero status code resulting from running a shell node.
777type exitStatus uint8
778
779func (s exitStatus) Error() string { return fmt.Sprintf("exit status %d", s) }
780
781// NewExitStatus creates an error which contains the specified exit status code.
782func NewExitStatus(status uint8) error {
783 return exitStatus(status)
784}
785
786// IsExitStatus checks whether error contains an exit status and returns it.
787func IsExitStatus(err error) (status uint8, ok bool) {
788 var s exitStatus
789 if errors.As(err, &s) {
790 return uint8(s), true
791 }
792 return 0, false
793}
794
795// Run interprets a node, which can be a [*File], [*Stmt], or [Command]. If a non-nil
796// error is returned, it will typically contain a command's exit status, which
797// can be retrieved with [IsExitStatus].
798//
799// Run can be called multiple times synchronously to interpret programs
800// incrementally. To reuse a [Runner] without keeping the internal shell state,
801// call Reset.
802//
803// Calling Run on an entire [*File] implies an exit, meaning that an exit trap may
804// run.
805func (r *Runner) Run(ctx context.Context, node syntax.Node) error {
806 if !r.didReset {
807 r.Reset()
808 }
809 r.fillExpandConfig(ctx)
810 r.err = nil
811 r.shellExited = false
812 r.filename = ""
813 switch node := node.(type) {
814 case *syntax.File:
815 r.filename = node.Name
816 r.stmts(ctx, node.Stmts)
817 if !r.shellExited {
818 r.exitShell(ctx, r.exit)
819 }
820 case *syntax.Stmt:
821 r.stmt(ctx, node)
822 case syntax.Command:
823 r.cmd(ctx, node)
824 default:
825 return fmt.Errorf("node can only be File, Stmt, or Command: %T", node)
826 }
827 if r.exit != 0 {
828 r.setErr(NewExitStatus(uint8(r.exit)))
829 }
830 if r.Vars != nil {
831 maps.Insert(r.Vars, r.writeEnv.Each)
832 }
833 return r.err
834}
835
836// Exited reports whether the last Run call should exit an entire shell. This
837// can be triggered by the "exit" built-in command, for example.
838//
839// Note that this state is overwritten at every Run call, so it should be
840// checked immediately after each Run call.
841func (r *Runner) Exited() bool {
842 return r.shellExited
843}
844
845// Subshell makes a copy of the given [Runner], suitable for use concurrently
846// with the original. The copy will have the same environment, including
847// variables and functions, but they can all be modified without affecting the
848// original.
849//
850// Subshell is not safe to use concurrently with [Run]. Orchestrating this is
851// left up to the caller; no locking is performed.
852//
853// To replace e.g. stdin/out/err, do StdIO(r.stdin, r.stdout, r.stderr)(r) on
854// the copy.
855func (r *Runner) Subshell() *Runner {
856 if !r.didReset {
857 r.Reset()
858 }
859 // Keep in sync with the Runner type. Manually copy fields, to not copy
860 // sensitive ones like [errgroup.Group], and to do deep copies of slices.
861 r2 := &Runner{
862 Dir: r.Dir,
863 tempDir: r.tempDir,
864 Params: r.Params,
865 callHandler: r.callHandler,
866 execHandler: r.execHandler,
867 openHandler: r.openHandler,
868 readDirHandler: r.readDirHandler,
869 statHandler: r.statHandler,
870 stdin: r.stdin,
871 stdout: r.stdout,
872 stderr: r.stderr,
873 filename: r.filename,
874 opts: r.opts,
875 usedNew: r.usedNew,
876 exit: r.exit,
877 lastExit: r.lastExit,
878
879 origStdout: r.origStdout, // used for process substitutions
880 }
881 // Funcs are copied, since they might be modified.
882 // Env vars aren't copied; setVar will copy lists and maps as needed.
883 oenv := &overlayEnviron{parent: r.writeEnv}
884 r2.writeEnv = oenv
885 r2.Funcs = maps.Clone(r.Funcs)
886 r2.Vars = make(map[string]expand.Variable)
887 r2.alias = maps.Clone(r.alias)
888
889 r2.dirStack = append(r2.dirBootstrap[:0], r.dirStack...)
890 r2.fillExpandConfig(r.ectx)
891 r2.didReset = true
892 return r2
893}