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 }