OILS / osh / cmd_eval.py View on Github | oils.pub

2623 lines, 1596 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9cmd_eval.py -- Interpreter for the command language.
10"""
11from __future__ import print_function
12
13import sys
14
15from _devbuild.gen.id_kind_asdl import Id, Id_t
16from _devbuild.gen.option_asdl import option_i, builtin_i
17from _devbuild.gen.syntax_asdl import (
18 IntParamBox,
19 loc,
20 loc_t,
21 loc_e,
22 Token,
23 CompoundWord,
24 command,
25 command_e,
26 command_t,
27 command_str,
28 condition,
29 condition_e,
30 condition_t,
31 case_arg,
32 case_arg_e,
33 case_arg_t,
34 BraceGroup,
35 Proc,
36 Func,
37 assign_op_e,
38 expr_t,
39 proc_sig,
40 proc_sig_e,
41 redir_param,
42 redir_param_e,
43 for_iter,
44 for_iter_e,
45 pat,
46 pat_e,
47 word,
48 Eggex,
49 List_of_command,
50 debug_frame,
51 VarDecl,
52 Mutation,
53 ExprCommand,
54 ShFunction,
55)
56from _devbuild.gen.runtime_asdl import (
57 cmd_value,
58 cmd_value_e,
59 CommandStatus,
60 flow_e,
61 RedirValue,
62 redirect_arg,
63 ProcArgs,
64 scope_e,
65 StatusArray,
66)
67from _devbuild.gen.types_asdl import redir_arg_type_e
68from _devbuild.gen.value_asdl import (value, value_e, value_t, y_lvalue,
69 y_lvalue_e, y_lvalue_t, LeftName, Obj)
70
71from core import bash_impl
72from core import dev
73from core import error
74from core import executor
75from core.error import e_die, e_die_status
76from core import num
77from core import pyos # Time(). TODO: rename
78from core import pyutil
79from core import state
80from display import ui
81from core import util
82from core import vm
83from frontend import args
84from frontend import consts
85from frontend import lexer
86from frontend import location
87from frontend import typed_args
88from osh import braces
89from osh import sh_expr_eval
90from osh import word_eval
91from mycpp import iolib
92from mycpp import mops
93from mycpp import mylib
94from mycpp.mylib import log, probe, switch, tagswitch, str_switch, NewDict
95from ysh import expr_eval
96from ysh import func_proc
97from ysh import val_ops
98
99import posix_ as posix
100import libc # for fnmatch
101# Import this name directly because the C++ translation uses macros literally.
102from libc import FNM_CASEFOLD
103
104from typing import List, Dict, Tuple, Optional, Any, cast, TYPE_CHECKING
105
106if TYPE_CHECKING:
107 from _devbuild.gen.option_asdl import builtin_t
108 from _devbuild.gen.runtime_asdl import cmd_value_t
109 from _devbuild.gen.syntax_asdl import Redir, EnvPair
110 from core.alloc import Arena
111 from core import optview
112 from core.vm import _Executor, _AssignBuiltin
113 from builtin import trap_osh
114
115# flags for main_loop.Batch, ExecuteAndCatch. TODO: Should probably in
116# ExecuteAndCatch, along with SetValue() flags.
117IsMainProgram = 1 << 0 # the main shell program, not eval/source/subshell
118RaiseControlFlow = 1 << 1 # eval/source builtins
119OptimizeSubshells = 1 << 2
120MarkLastCommands = 1 << 3
121NoDebugTrap = 1 << 4
122NoErrTrap = 1 << 5
123
124_STRICT_ERREXIT_COND_MSG = "Command conditionals should only have one status, not %s (strict_errexit, OILS-ERR-300)"
125
126
127def MakeBuiltinArgv(argv1):
128 # type: (List[str]) -> cmd_value.Argv
129 argv = [''] # dummy for argv[0]
130 argv.extend(argv1)
131 missing = None # type: CompoundWord
132 return cmd_value.Argv(argv, [missing] * len(argv), False, None, None)
133
134
135class Deps(object):
136
137 def __init__(self):
138 # type: () -> None
139 self.mutable_opts = None # type: state.MutableOpts
140 self.dumper = None # type: dev.CrashDumper
141 self.debug_f = None # type: util._DebugFile
142 self.cflow_builtin = None # type: ControlFlowBuiltin
143
144
145def _HasManyStatuses(node):
146 # type: (command_t) -> Optional[command_t]
147 """Code patterns that are bad for POSIX errexit. For YSH strict_errexit.
148
149 Note: strict_errexit also uses
150 shopt --unset _allow_command_sub _allow_process_sub
151 """
152 UP_node = node
153 with tagswitch(node) as case:
154 # Atoms.
155 # TODO: Do we need YSH atoms here?
156 if case(command_e.Simple, command_e.DBracket, command_e.DParen):
157 return None
158
159 elif case(command_e.Redirect):
160 node = cast(command.Redirect, UP_node)
161 return _HasManyStatuses(node.child)
162
163 elif case(command_e.Sentence):
164 # Sentence check is for if false; versus if false
165 node = cast(command.Sentence, UP_node)
166 return _HasManyStatuses(node.child)
167
168 elif case(command_e.Pipeline):
169 node = cast(command.Pipeline, UP_node)
170 if len(node.children) == 1:
171 # '! false' is a pipeline that we want to ALLOW
172 # '! ( echo subshell )' is DISALLWOED
173 return _HasManyStatuses(node.children[0])
174 else:
175 # Multiple parts like 'ls | wc' is disallowed
176 return node
177
178 elif case(command_e.AndOr):
179 node = cast(command.AndOr, UP_node)
180 for c in node.children:
181 if _HasManyStatuses(c):
182 return c
183 return None # otherwise allow 'if true && true; ...'
184
185 # - ShAssignment could be allowed, though its exit code will always be
186 # 0 without command subs
187 # - Naively, (non-singleton) pipelines could be allowed because pipefail.
188 # BUT could be a proc executed inside a child process, which causes a
189 # problem: the strict_errexit check has to occur at runtime and there's
190 # no way to signal it ot the parent.
191 return node
192
193
194def ListInitializeTarget(old_val,
195 has_plus,
196 exec_opts,
197 blame_loc,
198 destructive=True):
199 # type: (value_t, bool, optview.Exec, loc_t, bool) -> value_t
200 UP_old_val = old_val
201 with tagswitch(old_val) as case:
202 if case(value_e.Undef):
203 return bash_impl.BashArray_New()
204 elif case(value_e.Str):
205 if has_plus:
206 if exec_opts.strict_array():
207 e_die("Can't convert Str to BashArray (strict_array)",
208 blame_loc)
209 old_val = cast(value.Str, UP_old_val)
210 return bash_impl.BashArray_FromList([old_val.s])
211 else:
212 return bash_impl.BashArray_New()
213 elif case(value_e.BashArray):
214 old_val = cast(value.BashArray, UP_old_val)
215 if not destructive:
216 if has_plus:
217 old_val = bash_impl.BashArray_Copy(old_val)
218 else:
219 old_val = bash_impl.BashArray_New()
220 return old_val
221 elif case(value_e.BashAssoc):
222 old_val = cast(value.BashAssoc, UP_old_val)
223 if not destructive:
224 if has_plus:
225 old_val = bash_impl.BashAssoc_Copy(old_val)
226 else:
227 old_val = bash_impl.BashAssoc_New()
228 return old_val
229 else:
230 e_die(
231 "Can't list-initialize a value of type %s" %
232 ui.ValType(old_val), blame_loc)
233
234
235def ListInitialize(val, initializer, has_plus, exec_opts, blame_loc, arith_ev):
236 # type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, sh_expr_eval.ArithEvaluator) -> None
237 UP_val = val
238 with tagswitch(val) as case:
239 if case(value_e.BashArray):
240 val = cast(value.BashArray, UP_val)
241 bash_impl.BashArray_ListInitialize(val, initializer, has_plus,
242 blame_loc, arith_ev)
243 elif case(value_e.BashAssoc):
244 val = cast(value.BashAssoc, UP_val)
245 bash_impl.BashAssoc_ListInitialize(val, initializer, has_plus,
246 exec_opts, blame_loc)
247 else:
248 raise AssertionError(val.tag())
249
250
251def PlusEquals(old_val, val):
252 # type: (value_t, value_t) -> value_t
253 """Implement s+=val, typeset s+=val, etc."""
254
255 UP_old_val = old_val
256 UP_val = val
257
258 tag = val.tag()
259
260 with tagswitch(old_val) as case:
261 if case(value_e.Undef):
262 pass # val is RHS
263
264 elif case(value_e.Str):
265 if tag == value_e.Str:
266 old_val = cast(value.Str, UP_old_val)
267 str_to_append = cast(value.Str, UP_val)
268 val = value.Str(old_val.s + str_to_append.s)
269 else:
270 raise AssertionError() # parsing should prevent this
271
272 elif case(value_e.InternalStringArray, value_e.BashArray):
273 if tag == value_e.Str:
274 e_die("Can't append string to array")
275 else:
276 raise AssertionError() # parsing should prevent this
277
278 elif case(value_e.BashAssoc):
279 if tag == value_e.Str:
280 e_die("Can't append string to associative arrays")
281 else:
282 raise AssertionError() # parsing should prrevent this
283
284 else:
285 e_die("Can't append to value of type %s" % ui.ValType(old_val))
286
287 return val
288
289
290def _PrefixBindingsPersist(cmd_val):
291 # type: (cmd_value_t) -> bool
292 """
293 Note: I tried calculating this in EvalWordSequence2() and
294 SimpleEvalWordSequence2(). They have special hint_str logic for assignment
295 builtins.
296
297 But the hint_str doesn't respect word splitting, so it's better done here.
298 And it's annoying to have duplication in SimpleEvalWordSequence2().
299 """
300 UP_cmd_val = cmd_val
301 with tagswitch(cmd_val) as case:
302 if case(cmd_value_e.Assign):
303 # assignment builtins are special
304 return True
305 elif case(cmd_value_e.Argv):
306 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
307 if len(cmd_val.argv) == 0:
308 # This is counter-intuitive, but bash appears to preserve
309 # bindings in this case. Try the following snippet:
310 # bash -c 'A=a $B; echo $A' (prints "a")
311 return True
312
313 arg0 = cmd_val.argv[0]
314
315 # exec is an EXCEPTION to the SPECIAL builtin rule.
316 # FOO=bar exec sh -c 'echo $FOO' must work
317 #
318 # busybox ash does this too: it has a cmd_is_exec boolean.
319 if arg0 == 'exec':
320 return False
321 if consts.LookupSpecialBuiltin(arg0) != consts.NO_INDEX:
322 return True
323 return False
324
325
326def _ToInteger(s):
327 # type: (str) -> int
328 integer = int(s)
329
330 # Do extra range checking in Python. C++ to_int() in gc_builtins.py does
331 # this with INT_MAX and INT_MIN.
332 # TODO: move this to mylib.StringToInt32()?
333 # We don't want the check to depend on the machine architecture.
334 #if 0:
335 if mylib.PYTHON:
336 max_int = (1 << 31) - 1
337 min_int = -(1 << 31)
338 if not (min_int <= integer <= max_int):
339 raise ValueError()
340 return integer
341
342
343class ControlFlowBuiltin(vm._Builtin):
344
345 def __init__(
346 self,
347 mem, # type: state.Mem
348 exec_opts, # type: optview.Exec
349 tracer, # type: dev.Tracer
350 errfmt, # type: ui.ErrorFormatter
351 ):
352 self.mem = mem
353 self.exec_opts = exec_opts
354 self.tracer = tracer
355 self.errfmt = errfmt
356 self.loop_level = 0
357
358 def Static(self, keyword_id, keyword_str, keyword_loc, arg_str, arg_loc):
359 # type: (Id_t, str, loc_t, Optional[str], loc_t) -> int
360
361 if arg_str is not None:
362 # Quirk: We need 'return $empty' to be valid for libtool. This is
363 # another meaning of strict_control_flow, which also has to do with
364 # break/continue at top level. It has the side effect of making
365 # 'return ""' valid, which shells other than zsh fail on.
366 if (len(arg_str) == 0 and
367 not self.exec_opts.strict_control_flow()):
368 arg_int = 0
369 else:
370 try:
371 arg_int = _ToInteger(arg_str)
372 except ValueError:
373 # Either a bad argument, or integer overflow
374 e_die(
375 '%r expected a small integer, got %r' %
376 (keyword_str, arg_str), arg_loc)
377 # Note: there is another truncation to 256 in core/vm.py,
378 # but it does NOT cause an error.
379
380 else:
381 if keyword_id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
382 arg_int = self.mem.LastStatus()
383 else:
384 arg_int = 1 # break or continue 1 level by default
385
386 self.tracer.OnControlFlow(keyword_str, arg_int)
387
388 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
389 # from a sourced script, it makes sense to return from a main script.
390 if (self.loop_level == 0 and
391 keyword_id in (Id.ControlFlow_Break, Id.ControlFlow_Continue)):
392 msg = 'Invalid control flow at top level'
393 if self.exec_opts.strict_control_flow():
394 e_die(msg, keyword_loc)
395 else:
396 # Only print warnings, never fatal.
397 # Bash oddly only exits 1 for 'return', but no other shell does.
398 self.errfmt.PrefixPrint(msg, 'warning: ', keyword_loc)
399 return 0
400
401 if keyword_id == Id.ControlFlow_Exit:
402 # handled differently than other control flow
403 raise util.UserExit(arg_int)
404 else:
405 raise vm.IntControlFlow(keyword_id, keyword_str, keyword_loc,
406 arg_int)
407
408 def Run(self, cmd_val):
409 # type: (cmd_value.Argv) -> int
410 keyword_str = cmd_val.argv[0]
411 keyword_loc = cmd_val.arg_locs[0]
412
413 if self.exec_opts.strict_control_flow():
414 e_die(
415 "Control flow %r must be static in YSH (strict_control_flow)" %
416 keyword_str, keyword_loc)
417
418 arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
419 arg_r.Next() # Skip first
420
421 # Note: most shells allow break -- 2, so this isn't correct
422 arg_str, arg_loc = arg_r.Peek2()
423
424 # TODO: Can we get rid of str_switch? We calculated the builtin_id of
425 # type builtin_t in _RunSimpleCommand, but here we are using Id_t.
426 # (Id_t is static, and builtin_t is dynamic.)
427
428 # Returns builtin_t
429 #keyword_id = consts.LookupNormalBuiltin(keyword_str)
430
431 keyword_id = Id.Unknown_Tok
432 with str_switch(keyword_str) as case:
433 if case('break'):
434 keyword_id = Id.ControlFlow_Break
435 elif case('continue'):
436 keyword_id = Id.ControlFlow_Continue
437 elif case('return'):
438 keyword_id = Id.ControlFlow_Return
439 elif case('exit'):
440 keyword_id = Id.ControlFlow_Exit
441 else:
442 raise AssertionError()
443
444 return self.Static(keyword_id, keyword_str, keyword_loc, arg_str,
445 arg_loc)
446
447
448class ctx_LoopLevel(object):
449 """For checking for invalid control flow."""
450
451 def __init__(self, cflow):
452 # type: (ControlFlowBuiltin) -> None
453 cflow.loop_level += 1
454 self.cflow = cflow
455
456 def __enter__(self):
457 # type: () -> None
458 pass
459
460 def __exit__(self, type, value, traceback):
461 # type: (Any, Any, Any) -> None
462 self.cflow.loop_level -= 1
463
464
465class CommandEvaluator(object):
466 """Executes the program by tree-walking.
467
468 It also does some double-dispatch by passing itself into Eval() for
469 Compound/WordPart.
470 """
471
472 def __init__(
473 self,
474 mem, # type: state.Mem
475 exec_opts, # type: optview.Exec
476 errfmt, # type: ui.ErrorFormatter
477 procs, # type: state.Procs
478 assign_builtins, # type: Dict[builtin_t, _AssignBuiltin]
479 arena, # type: Arena
480 cmd_deps, # type: Deps
481 trap_state, # type: trap_osh.TrapState
482 signal_safe, # type: iolib.SignalSafe
483 ):
484 # type: (...) -> None
485 """
486 Args:
487 mem: Mem instance for storing variables
488 procs: dict of SHELL functions or 'procs'
489 builtins: dict of builtin callables
490 TODO: This should only be for assignment builtins?
491 cmd_deps: A bundle of stateless code
492 """
493 self.shell_ex = None # type: _Executor
494 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
495 self.bool_ev = None # type: sh_expr_eval.BoolEvaluator
496 self.expr_ev = None # type: expr_eval.ExprEvaluator
497 self.word_ev = None # type: word_eval.NormalWordEvaluator
498 self.tracer = None # type: dev.Tracer
499
500 self.mem = mem
501 # This is for shopt and set -o. They are initialized by flags.
502 self.exec_opts = exec_opts
503 self.errfmt = errfmt
504 self.procs = procs
505 self.assign_builtins = assign_builtins
506 self.arena = arena
507
508 self.mutable_opts = cmd_deps.mutable_opts
509 self.dumper = cmd_deps.dumper
510 self.debug_f = cmd_deps.debug_f # Used by ShellFuncAction too
511 self.cflow_builtin = cmd_deps.cflow_builtin
512
513 self.trap_state = trap_state
514 self.signal_safe = signal_safe
515
516 self.check_command_sub_status = False # a hack. Modified by ShellExecutor
517
518 self.status_array_pool = [] # type: List[StatusArray]
519
520 def CheckCircularDeps(self):
521 # type: () -> None
522 assert self.arith_ev is not None
523 assert self.bool_ev is not None
524 # TODO: re-enable this?
525 #assert self.expr_ev is not None
526 assert self.word_ev is not None
527
528 def _RunAssignBuiltin(self, cmd_val):
529 # type: (cmd_value.Assign) -> int
530 """Run an assignment builtin.
531
532 Except blocks copied from RunBuiltin.
533 """
534 builtin_id = cmd_val.builtin_id
535 # export has a different error that we don't mask
536 if (self.exec_opts.no_osh_builtins() and
537 builtin_id != builtin_i.export_):
538 e_die(
539 "The %s builtin isn't part of YSH (no_osh_builtins)" %
540 cmd_val.argv[0], cmd_val.arg_locs[0])
541
542 builtin_func = self.assign_builtins.get(builtin_id)
543 if builtin_func is None:
544 # This only happens with alternative Oils interpreters.
545 e_die("Assignment builtin %r not configured" % cmd_val.argv[0],
546 cmd_val.arg_locs[0])
547
548 io_errors = [] # type: List[error.IOError_OSError]
549 with vm.ctx_FlushStdout(io_errors):
550 with ui.ctx_Location(self.errfmt, cmd_val.arg_locs[0]):
551 try:
552 status = builtin_func.Run(cmd_val)
553 except (IOError, OSError) as e:
554 # e.g. declare -p > /dev/full
555 self.errfmt.PrintMessage(
556 '%s builtin I/O error: %s' %
557 (cmd_val.argv[0], pyutil.strerror(e)),
558 cmd_val.arg_locs[0])
559 return 1
560 except error.Usage as e: # Copied from RunBuiltin
561 arg0 = cmd_val.argv[0]
562 self.errfmt.PrefixPrint(e.msg, '%r ' % arg0, e.location)
563 return 2 # consistent error code for usage error
564
565 if len(io_errors): # e.g. declare -p > /dev/full
566 self.errfmt.PrintMessage(
567 '%s builtin I/O: %s' %
568 (cmd_val.argv[0], pyutil.strerror(io_errors[0])),
569 cmd_val.arg_locs[0])
570 return 1
571
572 return status
573
574 def _CheckStatus(self, status, cmd_st, node, default_loc):
575 # type: (int, CommandStatus, command_t, loc_t) -> None
576 """Raises error.ErrExit, maybe with location info attached."""
577
578 assert status >= 0, status
579
580 if status == 0:
581 return # Nothing to do
582
583 self._MaybeRunErrTrap()
584
585 if self.exec_opts.errexit():
586 # NOTE: Sometimes we print 2 errors
587 # - 'type -z' has a UsageError with location, then errexit
588 # - '> /nonexistent' has an I/O error, then errexit
589 # - Pipelines and subshells are compound. Commands within them fail.
590 # - however ( exit 33 ) only prints one message.
591 #
592 # But we will want something like 'false' to have location info.
593
594 UP_node = node
595 with tagswitch(node) as case:
596 if case(command_e.ShAssignment):
597 node = cast(command.ShAssignment, UP_node)
598 cmd_st.show_code = True # leaf
599 # Note: we show errors from assignments a=$(false) rarely: when
600 # errexit, inherit_errexit, verbose_errexit are on, but
601 # command_sub_errexit is off!
602
603 elif case(command_e.Subshell):
604 # Note: a subshell fails on it own with something like
605 # '( exit 2 )', not ( false ).
606 node = cast(command.Subshell, UP_node)
607 cmd_st.show_code = True # not sure about this, e.g. ( exit 42 )
608
609 elif case(command_e.Pipeline):
610 node = cast(command.Pipeline, UP_node)
611 cmd_st.show_code = True # not sure about this
612 # TODO: We should show which element of the pipeline failed!
613
614 with tagswitch(node) as case:
615 if case(command_e.Simple):
616 desc = 'Command'
617 else:
618 desc = command_str(node.tag(), dot=False)
619 msg = '%s failed with status %d' % (desc, status)
620
621 # Override location if explicitly passed.
622 # Note: this produces better results for process sub
623 # echo <(sort x)
624 # and different results for some pipelines:
625 # { ls; false; } | wc -l; echo hi # Point to | or first { ?
626 if default_loc.tag() != loc_e.Missing:
627 blame_loc = default_loc # type: loc_t
628 else:
629 blame_loc = location.TokenForCommand(node)
630
631 raise error.ErrExit(status,
632 msg,
633 blame_loc,
634 show_code=cmd_st.show_code)
635
636 def _EvalRedirect(self, r):
637 # type: (Redir) -> RedirValue
638
639 result = RedirValue(r.op.id, r.op, r.loc, None)
640
641 arg = r.arg
642 UP_arg = arg
643 with tagswitch(arg) as case:
644 if case(redir_param_e.Word):
645 arg_word = cast(CompoundWord, UP_arg)
646
647 # Note: needed for redirect like 'echo foo > x$LINENO'
648 self.mem.SetTokenForLine(r.op)
649
650 # Could be computed at parse time?
651 redir_type = consts.RedirArgType(r.op.id)
652
653 if redir_type == redir_arg_type_e.Path:
654 # Redirects with path arguments are evaluated in a special
655 # way. bash and zsh allow globbing a path, but
656 # dash/ash/mksh don't.
657 #
658 # If there are multiple files, zsh opens BOTH, but bash
659 # makes the command fail with status 1. We mostly follow
660 # bash behavior.
661
662 # These don't match bash/zsh behavior
663 # val = self.word_ev.EvalWordToString(arg_word)
664 # val, has_extglob = self.word_ev.EvalWordToPattern(arg_word)
665 # Short-circuit with word_.StaticEval() also doesn't work
666 # with globs
667
668 # mycpp needs this explicit declaration
669 b = braces.BraceDetect(
670 arg_word) # type: Optional[word.BracedTree]
671 if b is not None:
672 raise error.RedirectEval(
673 'Brace expansion not allowed (try adding quotes)',
674 arg_word)
675
676 # Needed for globbing behavior
677 files = self.word_ev.EvalWordSequence([arg_word])
678
679 n = len(files)
680 if n == 0:
681 # happens in OSH on empty elision
682 # in YSH because simple_word_eval globs to zero
683 raise error.RedirectEval(
684 "Can't redirect to zero files", arg_word)
685 if n > 1:
686 raise error.RedirectEval(
687 "Can't redirect to more than one file", arg_word)
688
689 result.arg = redirect_arg.Path(files[0])
690 return result
691
692 elif redir_type == redir_arg_type_e.Desc: # e.g. 1>&2, 1>&-, 1>&2-
693 val = self.word_ev.EvalWordToString(arg_word)
694 t = val.s
695 if len(t) == 0:
696 raise error.RedirectEval(
697 "Redirect descriptor can't be empty", arg_word)
698 return None
699
700 try:
701 if t == '-':
702 result.arg = redirect_arg.CloseFd
703 elif t[-1] == '-':
704 target_fd = int(t[:-1])
705 result.arg = redirect_arg.MoveFd(target_fd)
706 else:
707 result.arg = redirect_arg.CopyFd(int(t))
708 except ValueError:
709 raise error.RedirectEval(
710 'Invalid descriptor %r. Expected D, -, or D- where D is an '
711 'integer' % t, arg_word)
712 return None
713
714 return result
715
716 else:
717 raise AssertionError('Unknown redirect op')
718
719 elif case(redir_param_e.HereWord):
720 arg = cast(redir_param.HereWord, UP_arg)
721
722 val = self.word_ev.EvalWordToString(arg.w)
723 assert val.tag() == value_e.Str, val
724
725 assert r.op.id == Id.Redir_TLess, r.op
726 #print(arg_word)
727
728 # NOTE: bash and mksh both add \n for
729 # read <<< 'hi'
730 #
731 # YSH doesn't do this for multi-line strings:
732 # read <<< '''
733 # read <<< u'''
734 # read <<< """
735 if arg.is_multiline:
736 s = val.s
737 else:
738 s = val.s + '\n'
739
740 result.arg = redirect_arg.HereDoc(s)
741 return result
742
743 elif case(redir_param_e.HereDoc):
744 arg = cast(redir_param.HereDoc, UP_arg)
745 w = CompoundWord(
746 arg.stdin_parts) # HACK: Wrap it in a word to eval
747 val = self.word_ev.EvalWordToString(w)
748 assert val.tag() == value_e.Str, val
749 result.arg = redirect_arg.HereDoc(val.s)
750 return result
751
752 else:
753 raise AssertionError('Unknown redirect type')
754
755 raise AssertionError('for -Wreturn-type in C++')
756
757 def _RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
758 # type: (cmd_value_t, CommandStatus, int) -> int
759 """Private interface to run a simple command (including assignment)."""
760 UP_cmd_val = cmd_val
761 with tagswitch(UP_cmd_val) as case:
762 if case(cmd_value_e.Argv):
763 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
764 self.tracer.OnSimpleCommand(cmd_val.argv)
765 return self.shell_ex.RunSimpleCommand(cmd_val, cmd_st,
766 run_flags)
767
768 elif case(cmd_value_e.Assign):
769 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
770 self.tracer.OnAssignBuiltin(cmd_val)
771 return self._RunAssignBuiltin(cmd_val)
772
773 else:
774 raise AssertionError()
775
776 def _EvalTempEnv(self, more_env, flags):
777 # type: (List[EnvPair], int) -> None
778 """For FOO=1 cmd."""
779 for e_pair in more_env:
780 val = self.word_ev.EvalRhsWord(e_pair.val)
781
782 has_plus = False # We currently do not support tmpenv+=()
783 initializer = None # type: value.InitializerList
784 if val.tag() == value_e.InitializerList:
785 initializer = cast(value.InitializerList, val)
786
787 lval = LeftName(e_pair.name, e_pair.left)
788 old_val = sh_expr_eval.OldValue(
789 lval,
790 self.mem,
791 None, # No nounset
792 e_pair.left)
793 val = ListInitializeTarget(old_val,
794 has_plus,
795 self.exec_opts,
796 e_pair.left,
797 destructive=False)
798
799 # Set each var so the next one can reference it. Example:
800 # FOO=1 BAR=$FOO ls /
801 self.mem.SetNamed(location.LName(e_pair.name),
802 val,
803 scope_e.LocalOnly,
804 flags=flags)
805
806 if initializer is not None:
807 ListInitialize(val, initializer, has_plus, self.exec_opts,
808 e_pair.left, self.arith_ev)
809
810 def _StrictErrExit(self, node):
811 # type: (command_t) -> None
812 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
813 return
814
815 bad_node = _HasManyStatuses(node)
816 if bad_node:
817 node_str = ui.CommandType(bad_node)
818 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
819
820 def _StrictErrExitList(self, node_list):
821 # type: (List[command_t]) -> None
822 """Not allowed, too confusing:
823
824 if grep foo eggs.txt; grep bar eggs.txt; then echo hi fi
825 """
826 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
827 return
828
829 if len(node_list) > 1:
830 e_die(
831 "strict_errexit only allows a single command. Hint: use 'try'.",
832 loc.Command(node_list[0]))
833
834 assert len(node_list) > 0
835 node = node_list[0]
836 bad_node = _HasManyStatuses(node)
837 if bad_node:
838 node_str = ui.CommandType(bad_node)
839 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
840
841 def _EvalCondition(self, cond, blame_tok):
842 # type: (condition_t, Token) -> bool
843 """
844 Args:
845 spid: for OSH conditions, where errexit was disabled -- e.g. if
846 for YSH conditions, it would be nice to blame the ( instead
847 """
848 b = False
849 UP_cond = cond
850 #log('cond %r', type(cond))
851 #log('cond %r', cond)
852 with tagswitch(cond) as case:
853 if case(condition_e.Shell):
854 cond = cast(List_of_command, UP_cond)
855 self._StrictErrExitList(cond)
856 with state.ctx_ErrExit(self.mutable_opts, False, blame_tok):
857 cond_status = self._ExecuteList(cond)
858
859 b = cond_status == 0
860
861 elif case(condition_e.YshExpr):
862 cond = cast(condition.YshExpr, UP_cond)
863 obj = self.expr_ev.EvalExpr(cond.e, blame_tok)
864 b = val_ops.ToBool(obj)
865
866 return b
867
868 def _EvalCaseArg(self, arg, blame):
869 # type: (case_arg_t, loc_t) -> value_t
870 """Evaluate a `case_arg` into a `value_t` which can be matched on in a case
871 command.
872 """
873 UP_arg = arg
874 with tagswitch(arg) as case:
875 if case(case_arg_e.Word):
876 arg = cast(case_arg.Word, UP_arg)
877 return self.word_ev.EvalWordToString(arg.w)
878
879 elif case(case_arg_e.YshExpr):
880 arg = cast(case_arg.YshExpr, UP_arg)
881 return self.expr_ev.EvalExpr(arg.e, blame)
882
883 else:
884 raise NotImplementedError()
885
886 def _DoVarDecl(self, node):
887 # type: (VarDecl) -> int
888 # x = 'foo' in Hay blocks
889
890 flags = state.YshDecl
891
892 if node.keyword is None:
893 # Note: there's only one LHS
894 lhs0 = node.lhs[0]
895 lval = LeftName(lhs0.name, lhs0.left)
896 assert node.rhs is not None, node
897 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
898
899 flags |= state.SetReadOnly
900 self.mem.SetNamedYsh(lval, val, scope_e.LocalOnly, flags=flags)
901
902 else: # var or const
903 flags |= (state.SetReadOnly
904 if node.keyword.id == Id.KW_Const else 0)
905
906 # var x, y does null initialization
907 if node.rhs is None:
908 for i, lhs_val in enumerate(node.lhs):
909 lval = LeftName(lhs_val.name, lhs_val.left)
910 self.mem.SetNamedYsh(lval,
911 value.Null,
912 scope_e.LocalOnly,
913 flags=flags)
914 return 0
915
916 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
917 lvals = None # type: List[LeftName]
918 rhs_vals = None # type: List[value_t]
919
920 num_lhs = len(node.lhs)
921 if num_lhs == 1:
922 lhs0 = node.lhs[0]
923 lvals = [LeftName(lhs0.name, lhs0.left)]
924 rhs_vals = [right_val]
925 else:
926 items = val_ops.ToList(
927 right_val, 'Destructuring assignment expected List',
928 node.keyword)
929
930 num_rhs = len(items)
931 if num_lhs != num_rhs:
932 raise error.Expr(
933 'Got %d places on the left, but %d values on right' %
934 (num_lhs, num_rhs), node.keyword)
935
936 lvals = []
937 rhs_vals = []
938 for i, lhs_val in enumerate(node.lhs):
939 lval = LeftName(lhs_val.name, lhs_val.left)
940 lvals.append(lval)
941 rhs_vals.append(items[i])
942
943 for i, lval in enumerate(lvals):
944 rval = rhs_vals[i]
945 self.mem.SetNamedYsh(lval,
946 rval,
947 scope_e.LocalOnly,
948 flags=flags)
949
950 return 0
951
952 def _DoMutation(self, node):
953 # type: (Mutation) -> None
954
955 with switch(node.keyword.id) as case2:
956 if case2(Id.KW_SetVar):
957 which_scopes = scope_e.LocalOnly
958 elif case2(Id.KW_SetGlobal):
959 which_scopes = scope_e.GlobalOnly
960 else:
961 raise AssertionError(node.keyword.id)
962
963 if node.op.id == Id.Arith_Equal:
964 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
965
966 lvals = None # type: List[y_lvalue_t]
967 rhs_vals = None # type: List[value_t]
968
969 num_lhs = len(node.lhs)
970 if num_lhs == 1:
971 lvals = [self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)]
972 rhs_vals = [right_val]
973 else:
974 items = val_ops.ToList(
975 right_val, 'Destructuring assignment expected List',
976 node.keyword)
977
978 num_rhs = len(items)
979 if num_lhs != num_rhs:
980 raise error.Expr(
981 'Got %d places on the left, but %d values on the right'
982 % (num_lhs, num_rhs), node.keyword)
983
984 lvals = []
985 rhs_vals = []
986 for i, lhs_val in enumerate(node.lhs):
987 lvals.append(
988 self.expr_ev.EvalLhsExpr(lhs_val, which_scopes))
989 rhs_vals.append(items[i])
990
991 for i, lval in enumerate(lvals):
992 rval = rhs_vals[i]
993
994 # setvar mylist[0] = 42
995 # setvar mydict['key'] = 42
996 UP_lval = lval
997
998 if lval.tag() == y_lvalue_e.Local:
999 lval = cast(LeftName, UP_lval)
1000
1001 self.mem.SetNamedYsh(lval, rval, which_scopes)
1002
1003 elif lval.tag() == y_lvalue_e.Container:
1004 lval = cast(y_lvalue.Container, UP_lval)
1005
1006 obj = lval.obj
1007 UP_obj = obj
1008 with tagswitch(obj) as case:
1009 if case(value_e.List):
1010 obj = cast(value.List, UP_obj)
1011 index = expr_eval._ConvertToInt(
1012 lval.index, 'List index should be Int',
1013 loc.Missing)
1014 i = mops.BigTruncate(index)
1015 try:
1016 obj.items[i] = rval
1017 except IndexError:
1018 raise error.Expr('index out of range',
1019 loc.Missing)
1020
1021 elif case(value_e.Dict):
1022 obj = cast(value.Dict, UP_obj)
1023 key = val_ops.ToStr(lval.index,
1024 'Dict index should be Str',
1025 loc.Missing)
1026 obj.d[key] = rval
1027
1028 elif case(value_e.Obj):
1029 obj = cast(Obj, UP_obj)
1030 key = val_ops.ToStr(lval.index,
1031 'Obj index should be Str',
1032 loc.Missing)
1033 obj.d[key] = rval
1034
1035 else:
1036 raise error.TypeErr(
1037 obj, "obj[index] expected List, Dict, or Obj",
1038 loc.Missing)
1039
1040 else:
1041 raise AssertionError()
1042
1043 else:
1044 # Checked in the parser
1045 assert len(node.lhs) == 1
1046
1047 aug_lval = self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)
1048 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
1049
1050 self.expr_ev.EvalAugmented(aug_lval, val, node.op, which_scopes)
1051
1052 def _DoSimple(self, node, cmd_st):
1053 # type: (command.Simple, CommandStatus) -> int
1054 probe('cmd_eval', '_DoSimple_enter')
1055
1056 # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
1057 # redirected here, which screws up logging. For example, 'echo hi
1058 # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying
1059 # redirects.
1060
1061 # Another problem:
1062 # - tracing can be called concurrently from multiple processes, leading
1063 # to overlap. Maybe have a mode that creates a file per process.
1064 # xtrace-proc
1065 # - line numbers for every command would be very nice. But then you have
1066 # to print the filename too.
1067
1068 words = braces.BraceExpandWords(node.words)
1069
1070 # Note: Individual WORDS can fail
1071 # - $() and <() can have failures. This can happen in DBracket,
1072 # DParen, etc. too
1073 # - Tracing: this can start processes for proc sub and here docs!
1074 cmd_val = self.word_ev.EvalWordSequence2(words,
1075 node.is_last_cmd,
1076 allow_assign=True)
1077
1078 UP_cmd_val = cmd_val
1079 if UP_cmd_val.tag() == cmd_value_e.Argv:
1080 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
1081
1082 if len(cmd_val.argv): # it can be empty in rare cases
1083 self.mem.SetLastArgument(cmd_val.argv[-1])
1084 else:
1085 self.mem.SetLastArgument('')
1086
1087 if node.typed_args or node.block: # guard to avoid allocs
1088 cmd_val.proc_args = ProcArgs(node.typed_args, None, None, None)
1089 func_proc.EvalTypedArgsToProc(self.expr_ev,
1090 self.mem.CurrentFrame(),
1091 self.mem.GlobalFrame(),
1092 self.mutable_opts, node,
1093 cmd_val.proc_args)
1094 else:
1095 if node.block:
1096 e_die("ShAssignment builtins don't accept blocks",
1097 node.block.brace_group.left)
1098 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
1099
1100 # Could reset $_ after assignment, but then we'd have to do it for
1101 # all YSH constructs too. It's easier to let it persist. Other
1102 # shells aren't consistent.
1103 # self.mem.SetLastArgument('')
1104
1105 run_flags = executor.IS_LAST_CMD if node.is_last_cmd else 0
1106
1107 status = self._EvalAndPushRedirects(node.redirects)
1108 if status != 0:
1109 return status
1110
1111 num_redirects = 0
1112 if node.redirects is not None:
1113 num_redirects = len(node.redirects)
1114
1115 io_errors = [] # type: List[int]
1116 with vm.ctx_Redirect(self.shell_ex, num_redirects, io_errors):
1117 # NOTE: RunSimpleCommand may never return
1118 if len(node.more_env): # I think this guard is necessary?
1119 if self.exec_opts.env_obj(): # YSH
1120 bindings = NewDict() # type: Dict[str, value_t]
1121 with state.ctx_EnclosedFrame(self.mem,
1122 self.mem.CurrentFrame(),
1123 self.mem.GlobalFrame(),
1124 bindings):
1125 self._EvalTempEnv(node.more_env, 0)
1126
1127 # Push this on the prototype chain
1128 with state.ctx_EnvObj(self.mem, bindings):
1129 status = self._RunSimpleCommand(
1130 cmd_val, cmd_st, run_flags)
1131
1132 else: # OSH
1133 if _PrefixBindingsPersist(cmd_val):
1134 if self.exec_opts.strict_env_binding():
1135 # Disallow pitfall in YSH
1136 first_pair = node.more_env[0]
1137 e_die(
1138 "Special builtins can't have prefix bindings (OILS-ERR-302)",
1139 first_pair.left)
1140
1141 # Special builtins (except exec) have their temp env persisted.
1142 # TODO: Disallow this in YSH? It should just be a
1143 # top-level assignment.
1144 self._EvalTempEnv(node.more_env, 0)
1145 status = self._RunSimpleCommand(
1146 cmd_val, cmd_st, run_flags)
1147 else:
1148 with state.ctx_Temp(self.mem):
1149 self._EvalTempEnv(node.more_env, state.SetExport)
1150 status = self._RunSimpleCommand(
1151 cmd_val, cmd_st, run_flags)
1152 else:
1153 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
1154
1155 if len(io_errors):
1156 # It would be better to point to the right redirect
1157 # operator, but we don't track it specifically
1158 e_die("Fatal error popping redirect: %s" %
1159 posix.strerror(io_errors[0]))
1160
1161 probe('cmd_eval', '_DoSimple_exit', status)
1162 return status
1163
1164 def _DoExpandedAlias(self, node):
1165 # type: (command.ExpandedAlias) -> int
1166 # Expanded aliases need redirects and env bindings from the calling
1167 # context, as well as redirects in the expansion!
1168
1169 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
1170 # expansion, since aliases are discouraged.
1171
1172 if len(node.more_env):
1173 with state.ctx_Temp(self.mem):
1174 self._EvalTempEnv(node.more_env, state.SetExport)
1175 return self._Execute(node.child)
1176 else:
1177 return self._Execute(node.child)
1178
1179 def _DoPipeline(self, node, cmd_st):
1180 # type: (command.Pipeline, CommandStatus) -> int
1181 cmd_st.check_errexit = True
1182 for op in node.ops:
1183 if op.id != Id.Op_Pipe:
1184 e_die("|& isn't supported", op)
1185
1186 # Remove $_ before pipeline. This matches bash, and is important in
1187 # pipelines than assignments because pipelines are non-deterministic.
1188 self.mem.SetLastArgument('')
1189
1190 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
1191 # which _Execute() boils down into a status for us.
1192 status = -1
1193
1194 if node.negated is not None:
1195 self._StrictErrExit(node)
1196 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
1197 # '! grep' is parsed as a pipeline, according to the grammar, but
1198 # there's no pipe() call.
1199 if len(node.children) == 1:
1200 tmp_status = self._Execute(node.children[0])
1201 status = 1 if tmp_status == 0 else 0
1202 else:
1203 self.shell_ex.RunPipeline(node, cmd_st)
1204 cmd_st.pipe_negated = True
1205
1206 # errexit is disabled for !.
1207 cmd_st.check_errexit = False
1208 else:
1209 self.shell_ex.RunPipeline(node, cmd_st)
1210
1211 return status
1212
1213 def _DoShAssignment(self, node, cmd_st):
1214 # type: (command.ShAssignment, CommandStatus) -> int
1215 assert len(node.pairs) >= 1, node
1216
1217 # x=y is 'neutered' inside 'proc'
1218 which_scopes = self.mem.ScopesForWriting()
1219
1220 for pair in node.pairs:
1221 # The shell assignments should always have RHS. An AssignPair
1222 # stored in command_e.ShAssignment is constructed in
1223 # cmd_parse._MakeAssignPair, where rhs is explicitly constructed to
1224 # be CompoundWord or rhs_word.Empty.
1225 assert pair.rhs, pair.rhs
1226
1227 # RHS can be a string or initializer list.
1228 rhs = self.word_ev.EvalRhsWord(pair.rhs)
1229 assert isinstance(rhs, value_t), rhs
1230
1231 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
1232 has_plus = pair.op == assign_op_e.PlusEqual
1233
1234 initializer = None # type: value.InitializerList
1235 if rhs.tag() == value_e.InitializerList:
1236 initializer = cast(value.InitializerList, rhs)
1237
1238 # If rhs is an initializer list, we perform the initialization
1239 # of LHS.
1240 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1241 node.left)
1242
1243 val = ListInitializeTarget(old_val, has_plus, self.exec_opts,
1244 pair.left)
1245
1246 elif has_plus:
1247 # do not respect set -u
1248 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1249 node.left)
1250
1251 val = PlusEquals(old_val, rhs)
1252
1253 elif pair.rhs:
1254 # plain assignment
1255 val = rhs
1256
1257 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
1258 # with Undef value, but the 'array' attribute.
1259
1260 flags = 0 # for tracing
1261 # set -a: automatically mark variables for export
1262 if self.exec_opts.allexport():
1263 flags |= state.SetExport
1264 self.mem.SetValue(lval, val, which_scopes, flags=flags)
1265 if initializer is not None:
1266 ListInitialize(val, initializer, has_plus, self.exec_opts,
1267 pair.left, self.arith_ev)
1268
1269 self.tracer.OnShAssignment(lval, pair.op, rhs, flags, which_scopes)
1270
1271 # For the cases like
1272 # a=$(false)
1273 # $(false) $(exit 42)
1274 if self.check_command_sub_status:
1275 last_status = self.mem.LastStatus()
1276 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
1277 return last_status # A global assignment shouldn't clear $?.
1278 else:
1279 return 0
1280
1281 def _DoExpr(self, node):
1282 # type: (ExprCommand) -> int
1283
1284 # call f(x) or = f(x)
1285 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
1286
1287 if node.keyword.id == Id.Lit_Equals: # = f(x)
1288 io_errors = [] # type: List[error.IOError_OSError]
1289 with vm.ctx_FlushStdout(io_errors):
1290 try:
1291 ui.PrettyPrintValue('', val, mylib.Stdout())
1292 except (IOError, OSError) as e:
1293 self.errfmt.PrintMessage(
1294 'I/O error during = keyword: %s' % pyutil.strerror(e),
1295 node.keyword)
1296 return 1
1297
1298 if len(io_errors): # e.g. disk full, ulimit
1299 self.errfmt.PrintMessage(
1300 'I/O error during = keyword: %s' %
1301 pyutil.strerror(io_errors[0]), node.keyword)
1302 return 1
1303
1304 return 0
1305
1306 def _DoControlFlow(self, node):
1307 # type: (command.ControlFlow) -> int
1308 """
1309 Note: YSH control flow is static, while OSH control flow may be dynamic
1310 (without shopt -s strict_control_flow)
1311
1312 One reason is to have a static control flow graph, so we could possibly
1313 compile shell to bytecode. That is MAYBE a step toward getting rid of
1314 the "C++ exceptions are slow when they throw" problem.
1315 """
1316 w = node.arg_word
1317 if w: # Evaluate the argument
1318 arg_str = self.word_ev.EvalWordToString(w).s
1319 arg_loc = w # type: loc_t
1320 else:
1321 arg_str = None
1322 arg_loc = loc.Missing
1323
1324 keyword = node.keyword
1325 # This is static, so we could also use lexer.TokenVal()
1326 keyword_str = consts.ControlFlowName(keyword.id)
1327
1328 return self.cflow_builtin.Static(keyword.id, keyword_str, keyword,
1329 arg_str, arg_loc)
1330
1331 def _DoAndOr(self, node, cmd_st):
1332 # type: (command.AndOr, CommandStatus) -> int
1333 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1334 # in dbracket.test.sh.
1335
1336 left = node.children[0]
1337
1338 # Suppress failure for every child except the last one.
1339 self._StrictErrExit(left)
1340 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1341 status = self._Execute(left)
1342
1343 i = 1
1344 n = len(node.children)
1345 while i < n:
1346 #log('i %d status %d', i, status)
1347 child = node.children[i]
1348 op = node.ops[i - 1]
1349 op_id = op.id
1350
1351 #log('child %s op_id %s', child, op_id)
1352
1353 if op_id == Id.Op_DPipe and status == 0:
1354 i += 1
1355 continue # short circuit
1356
1357 elif op_id == Id.Op_DAmp and status != 0:
1358 i += 1
1359 continue # short circuit
1360
1361 if i == n - 1: # errexit handled differently for last child
1362 status = self._Execute(child)
1363 else:
1364 # blame the right && or ||
1365 self._StrictErrExit(child)
1366 with state.ctx_ErrExit(self.mutable_opts, False, op):
1367 status = self._Execute(child)
1368
1369 i += 1
1370
1371 return status
1372
1373 def _DoWhileUntil(self, node):
1374 # type: (command.WhileUntil) -> int
1375 status = 0
1376 with ctx_LoopLevel(self.cflow_builtin):
1377 while True:
1378 try:
1379 # blame while/until spid
1380 b = self._EvalCondition(node.cond, node.keyword)
1381 if node.keyword.id == Id.KW_Until:
1382 b = not b
1383 if not b:
1384 break
1385 status = self._Execute(node.body) # last one wins
1386
1387 except vm.IntControlFlow as e:
1388 status = 0
1389 action = e.HandleLoop()
1390 if action == flow_e.Break:
1391 break
1392 elif action == flow_e.Raise:
1393 raise
1394
1395 return status
1396
1397 def _DoForEach(self, node):
1398 # type: (command.ForEach) -> int
1399
1400 # for the 2 kinds of shell loop
1401 iter_list = None # type: List[str]
1402
1403 # for YSH loop
1404 iter_expr = None # type: expr_t
1405 expr_blame = None # type: loc_t
1406
1407 iterable = node.iterable
1408 UP_iterable = iterable
1409
1410 with tagswitch(node.iterable) as case:
1411 if case(for_iter_e.Args):
1412 iter_list = self.mem.GetArgv()
1413
1414 elif case(for_iter_e.Words):
1415 iterable = cast(for_iter.Words, UP_iterable)
1416 words = braces.BraceExpandWords(iterable.words)
1417 iter_list = self.word_ev.EvalWordSequence(words)
1418
1419 elif case(for_iter_e.YshExpr):
1420 iterable = cast(for_iter.YshExpr, UP_iterable)
1421 iter_expr = iterable.e
1422 expr_blame = iterable.blame
1423
1424 else:
1425 raise AssertionError()
1426
1427 n = len(node.iter_names)
1428 assert n > 0
1429
1430 i_name = None # type: Optional[LeftName]
1431 # required
1432 name1 = None # type: LeftName
1433 name2 = None # type: Optional[LeftName]
1434
1435 it2 = None # type: val_ops.Iterator
1436 if iter_expr: # for_expr.YshExpr
1437 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1438
1439 UP_val = val
1440 with tagswitch(val) as case:
1441 if case(value_e.List):
1442 val = cast(value.List, UP_val)
1443 it2 = val_ops.ListIterator(val)
1444
1445 if n == 1:
1446 name1 = location.LName(node.iter_names[0])
1447 elif n == 2:
1448 i_name = location.LName(node.iter_names[0])
1449 name1 = location.LName(node.iter_names[1])
1450 else:
1451 # This is similar to a parse error
1452 e_die_status(
1453 2,
1454 'List iteration expects at most 2 loop variables',
1455 node.keyword)
1456
1457 elif case(value_e.Dict):
1458 val = cast(value.Dict, UP_val)
1459 it2 = val_ops.DictIterator(val)
1460
1461 if n == 1:
1462 name1 = location.LName(node.iter_names[0])
1463 elif n == 2:
1464 name1 = location.LName(node.iter_names[0])
1465 name2 = location.LName(node.iter_names[1])
1466 elif n == 3:
1467 i_name = location.LName(node.iter_names[0])
1468 name1 = location.LName(node.iter_names[1])
1469 name2 = location.LName(node.iter_names[2])
1470 else:
1471 raise AssertionError()
1472
1473 elif case(value_e.Range):
1474 val = cast(value.Range, UP_val)
1475 it2 = val_ops.RangeIterator(val)
1476
1477 if n == 1:
1478 name1 = location.LName(node.iter_names[0])
1479 elif n == 2:
1480 i_name = location.LName(node.iter_names[0])
1481 name1 = location.LName(node.iter_names[1])
1482 else:
1483 e_die_status(
1484 2,
1485 'Range iteration expects at most 2 loop variables',
1486 node.keyword)
1487
1488 elif case(value_e.Stdin):
1489 # TODO: This could changed to magic iterator?
1490 it2 = val_ops.StdinIterator(expr_blame)
1491 if n == 1:
1492 name1 = location.LName(node.iter_names[0])
1493 elif n == 2:
1494 i_name = location.LName(node.iter_names[0])
1495 name1 = location.LName(node.iter_names[1])
1496 else:
1497 e_die_status(
1498 2,
1499 'Stdin iteration expects at most 2 loop variables',
1500 node.keyword)
1501 else:
1502 raise error.TypeErr(
1503 val, 'for loop expected List, Dict, Range, or Stdin',
1504 node.keyword)
1505
1506 else:
1507 assert iter_list is not None, iter_list
1508
1509 #log('iter list %s', iter_list)
1510 it2 = val_ops.ArrayIter(iter_list)
1511
1512 if n == 1:
1513 name1 = location.LName(node.iter_names[0])
1514 elif n == 2:
1515 i_name = location.LName(node.iter_names[0])
1516 name1 = location.LName(node.iter_names[1])
1517 else:
1518 # This is similar to a parse error
1519 e_die_status(
1520 2, 'Argv iteration expects at most 2 loop variables',
1521 node.keyword)
1522
1523 status = 0 # in case we loop zero times
1524 with ctx_LoopLevel(self.cflow_builtin):
1525 while True:
1526 with state.ctx_LoopFrame(self.mem,
1527 self.exec_opts.for_loop_frames()):
1528 first = it2.FirstValue()
1529 #log('first %s', first)
1530 if first is None: # for StdinIterator
1531 #log('first is None')
1532 break
1533
1534 if first.tag() == value_e.Interrupted:
1535 self.RunPendingTraps()
1536 #log('Done running traps')
1537 continue
1538
1539 self.mem.SetLocalName(name1, first)
1540 if name2:
1541 self.mem.SetLocalName(name2, it2.SecondValue())
1542 if i_name:
1543 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1544
1545 # increment index before handling continue, etc.
1546 it2.Next()
1547
1548 try:
1549 status = self._Execute(node.body) # last one wins
1550 except vm.IntControlFlow as e:
1551 status = 0
1552 action = e.HandleLoop()
1553 if action == flow_e.Break:
1554 break
1555 elif action == flow_e.Raise:
1556 raise
1557
1558 return status
1559
1560 def _DoForExpr(self, node):
1561 # type: (command.ForExpr) -> int
1562
1563 status = 0
1564
1565 init = node.init
1566 for_cond = node.cond
1567 body = node.body
1568 update = node.update
1569
1570 self.arith_ev.Eval(init)
1571 with ctx_LoopLevel(self.cflow_builtin):
1572 while True:
1573 # We only accept integers as conditions
1574 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1575 if mops.Equal(cond_int, mops.ZERO): # false
1576 break
1577
1578 try:
1579 status = self._Execute(body)
1580 except vm.IntControlFlow as e:
1581 status = 0
1582 action = e.HandleLoop()
1583 if action == flow_e.Break:
1584 break
1585 elif action == flow_e.Raise:
1586 raise
1587
1588 self.arith_ev.Eval(update)
1589
1590 return status
1591
1592 def _DoShFunction(self, node):
1593 # type: (ShFunction) -> None
1594
1595 # Note: shell functions don't have a captured frame. TODO: Get rid of
1596 # GlobalFrame as well. I think shell functions will be disallowed in
1597 # pure YSH. They appear in the __sh_funcs__, NOT in modules!
1598
1599 sh_func = value.Proc(node.name, node.name_tok,
1600 proc_sig.Open, node.body, None, True, None,
1601 self.mem.GlobalFrame(), node.code_str)
1602 self.procs.DefineShellFunc(node.name, sh_func)
1603
1604 def _DoProc(self, node):
1605 # type: (Proc) -> None
1606 proc_name = lexer.TokenVal(node.name)
1607
1608 if node.sig.tag() == proc_sig_e.Closed:
1609 sig = cast(proc_sig.Closed, node.sig)
1610 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1611 else:
1612 proc_defaults = None
1613
1614 # no dynamic scope
1615 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1616 proc_defaults, False, self.mem.CurrentFrame(),
1617 self.mem.GlobalFrame(), None)
1618 self.procs.DefineProc(proc_name, proc)
1619
1620 def _DoFunc(self, node):
1621 # type: (Func) -> None
1622 name = lexer.TokenVal(node.name)
1623 lval = location.LName(name)
1624
1625 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1626 self.expr_ev, node)
1627 func_val = value.Func(name, node, pos_defaults, named_defaults,
1628 self.mem.CurrentFrame(), self.mem.GlobalFrame())
1629
1630 # TODO: I'm not observing a difference with the YshDecl flag? That
1631 # should prevent the parent scope from being modified.
1632 #self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly, flags=state.YshDecl)
1633 self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly)
1634
1635 def _DoIf(self, node):
1636 # type: (command.If) -> int
1637 status = -1
1638
1639 done = False
1640 for if_arm in node.arms:
1641 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1642 if b:
1643 status = self._ExecuteList(if_arm.action)
1644 done = True
1645 break
1646
1647 if not done and node.else_action is not None:
1648 status = self._ExecuteList(node.else_action)
1649
1650 assert status != -1, 'Should have been initialized'
1651 return status
1652
1653 def _DoCase(self, node):
1654 # type: (command.Case) -> int
1655
1656 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1657 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1658
1659 status = 0 # If there are no arms, it should be zero?
1660
1661 done = False # Should we try the next arm?
1662
1663 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1664 ignore_next_cond = False
1665
1666 for case_arm in node.arms:
1667 with tagswitch(case_arm.pattern) as case:
1668 if case(pat_e.Words):
1669 if to_match.tag() != value_e.Str:
1670 continue # A non-string `to_match` will never match a pat.Words
1671 to_match_str = cast(value.Str, to_match)
1672
1673 pat_words = cast(pat.Words, case_arm.pattern)
1674
1675 this_arm_matches = False
1676 if ignore_next_cond: # Special handling for ;&
1677 this_arm_matches = True
1678 ignore_next_cond = False
1679 else:
1680 for pat_word in pat_words.words:
1681 word_val = self.word_ev.EvalWordToString(
1682 pat_word, word_eval.QUOTE_FNMATCH)
1683
1684 if libc.fnmatch(word_val.s, to_match_str.s,
1685 fnmatch_flags):
1686 this_arm_matches = True
1687 break # Stop at first pattern
1688
1689 if this_arm_matches:
1690 status = self._ExecuteList(case_arm.action)
1691 done = True
1692
1693 # ;& and ;;& only apply to shell-style case
1694 if case_arm.right:
1695 id_ = case_arm.right.id
1696 if id_ == Id.Op_SemiAmp:
1697 # very weird semantic
1698 ignore_next_cond = True
1699 done = False
1700 elif id_ == Id.Op_DSemiAmp:
1701 # Keep going until next pattern
1702 done = False
1703
1704 elif case(pat_e.YshExprs):
1705 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1706
1707 for pat_expr in pat_exprs.exprs:
1708 expr_val = self.expr_ev.EvalExpr(
1709 pat_expr, case_arm.left)
1710
1711 if val_ops.ExactlyEqual(expr_val, to_match,
1712 case_arm.left):
1713 status = self._ExecuteList(case_arm.action)
1714 done = True
1715 break
1716
1717 elif case(pat_e.Eggex):
1718 eggex = cast(Eggex, case_arm.pattern)
1719 eggex_val = self.expr_ev.EvalEggex(eggex)
1720
1721 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1722 status = self._ExecuteList(case_arm.action)
1723 done = True
1724 break
1725
1726 elif case(pat_e.Else):
1727 status = self._ExecuteList(case_arm.action)
1728 done = True
1729 break
1730
1731 else:
1732 raise AssertionError()
1733
1734 if done: # first match wins
1735 break
1736
1737 return status
1738
1739 def _DoTimeBlock(self, node):
1740 # type: (command.TimeBlock) -> int
1741 # TODO:
1742 # - When do we need RUSAGE_CHILDREN?
1743 # - Respect TIMEFORMAT environment variable.
1744 # "If this variable is not set, Bash acts as if it had the value"
1745 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1746 # "A trailing newline is added when the format string is displayed."
1747
1748 s_real, s_user, s_sys = pyos.Time()
1749 status = self._Execute(node.pipeline)
1750 e_real, e_user, e_sys = pyos.Time()
1751 # note: mycpp doesn't support %.3f
1752 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1753
1754 return status
1755
1756 def _EvalAndPushRedirects(self, redirects):
1757 # type: (List[Redir]) -> int
1758 if redirects is None:
1759 return 0
1760
1761 status = 0
1762 redir_vals = [] # type: List[RedirValue]
1763
1764 try:
1765 for redir in redirects:
1766 redir_vals.append(self._EvalRedirect(redir))
1767 except error.RedirectEval as e:
1768 self.errfmt.PrettyPrintError(e)
1769 redir_vals = None
1770 except error.WordFailure as e:
1771 # This happens e.g. with the following cases:
1772 #
1773 # $ echo hi > foo-* # with failglob (FailGlob)
1774 # $ echo > ${!undef} # (VarSubFailure)
1775 #
1776 if not e.HasLocation():
1777 e.location = self.mem.GetFallbackLocation()
1778 self.errfmt.PrettyPrintError(e)
1779 redir_vals = None
1780
1781 if redir_vals is None:
1782 # Error evaluating redirect words
1783 status = 1
1784
1785 # If we evaluated redir_vals, apply/push them
1786 if status == 0:
1787 io_errors = [] # type: List[int]
1788 self.shell_ex.PushRedirects(redir_vals, io_errors)
1789 if len(io_errors):
1790 # core/process.py prints cryptic errors, so we repeat them
1791 # here. e.g. Bad File Descriptor
1792 self.errfmt.PrintMessage(
1793 'I/O error applying redirect: %s' %
1794 posix.strerror(io_errors[0]),
1795 self.mem.GetFallbackLocation())
1796 status = 1
1797
1798 return status
1799
1800 def _DoRedirect(self, node, cmd_st):
1801 # type: (command.Redirect, CommandStatus) -> int
1802 status = self._EvalAndPushRedirects(node.redirects)
1803 if status != 0:
1804 return status
1805
1806 # If we applied redirects successfully, run the command_t, and pop
1807 # them.
1808
1809 # Translation fix: redirect I/O errors may happen in a C++
1810 # destructor ~vm::ctx_Redirect, which means they must be signaled
1811 # by out params, not exceptions.
1812 io_errors = [] # type: List[int]
1813 with vm.ctx_Redirect(self.shell_ex, len(node.redirects), io_errors):
1814 status = self._Execute(node.child)
1815 if len(io_errors):
1816 # It would be better to point to the right redirect
1817 # operator, but we don't track it specifically
1818 e_die("Fatal error popping redirect: %s" %
1819 posix.strerror(io_errors[0]))
1820
1821 return status
1822
1823 def _LeafTick(self):
1824 # type: () -> None
1825 """Do periodic work while executing shell.
1826
1827 We may run traps, check for Ctrl-C, or garbage collect.
1828 """
1829 self.RunPendingTraps()
1830 if self.signal_safe.PollUntrappedSigInt():
1831 raise KeyboardInterrupt()
1832
1833 # Manual GC point before every statement
1834 mylib.MaybeCollect()
1835
1836 if 0:
1837
1838 def _DispatchFast(self, node, cmd_st):
1839 # type: (command_t, CommandStatus) -> int
1840 """
1841 TODO: protoype YSH fast_cmd_t
1842
1843 var x = 5
1844 while (x > 0) {
1845 echo "x = $x"
1846 if (x === 3) {
1847 break
1848 }
1849 setvar x -= 1
1850 }
1851
1852 Nodes to compile:
1853 While, If, ControlFlow
1854 While has BraceGroup body though
1855 New nodes:
1856 Jump(int index) # index into what?
1857 JumpIfFalse( ? ) # this usually takes the top of stack?
1858
1859 Nodes that stay the same:
1860 VarDecl, Mutation, Simple
1861
1862 Do a very simple interpretation, ignoring ctx_*
1863
1864 And then introduce mem.exec_stack ? These are like the Python VMs frame.
1865
1866 - Second option: return an enum
1867 """
1868 pass
1869
1870 def _Dispatch(self, node, cmd_st):
1871 # type: (command_t, CommandStatus) -> int
1872 """Switch on the command_t variants and execute them."""
1873
1874 # If we call RunCommandSub in a recursive call to the executor, this will
1875 # be set true (if strict_errexit is false). But it only lasts for one
1876 # command.
1877 probe('cmd_eval', '_Dispatch', node.tag())
1878 self.check_command_sub_status = False
1879
1880 UP_node = node
1881 with tagswitch(node) as case:
1882 if case(command_e.Simple): # LEAF command
1883 node = cast(command.Simple, UP_node)
1884
1885 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1886 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1887 # TODO: blame_tok should always be set.
1888 if node.blame_tok is not None:
1889 self.mem.SetTokenForLine(node.blame_tok)
1890
1891 self._MaybeRunDebugTrap()
1892 cmd_st.check_errexit = True
1893 status = self._DoSimple(node, cmd_st)
1894 self._LeafTick()
1895
1896 elif case(command_e.ShAssignment): # LEAF command
1897 node = cast(command.ShAssignment, UP_node)
1898
1899 self.mem.SetTokenForLine(node.pairs[0].left)
1900 self._MaybeRunDebugTrap()
1901
1902 # Only unqualified assignment a=b
1903 status = self._DoShAssignment(node, cmd_st)
1904 self._LeafTick()
1905
1906 elif case(command_e.Sentence): # NOT leaf, but put it up front
1907 node = cast(command.Sentence, UP_node)
1908
1909 # Don't check_errexit since this isn't a leaf command
1910 if node.terminator.id == Id.Op_Semi:
1911 status = self._Execute(node.child)
1912 else:
1913 status = self.shell_ex.RunBackgroundJob(node.child)
1914
1915 elif case(command_e.DBracket): # LEAF command
1916 node = cast(command.DBracket, UP_node)
1917
1918 self.mem.SetTokenForLine(node.left)
1919 self._MaybeRunDebugTrap()
1920
1921 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1922
1923 cmd_st.check_errexit = True
1924 cmd_st.show_code = True # this is a "leaf" for errors
1925 result = self.bool_ev.EvalB(node.expr)
1926 status = 0 if result else 1
1927 self._LeafTick()
1928
1929 elif case(command_e.DParen): # LEAF command
1930 node = cast(command.DParen, UP_node)
1931
1932 self.mem.SetTokenForLine(node.left)
1933 self._MaybeRunDebugTrap()
1934
1935 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1936
1937 cmd_st.check_errexit = True
1938 cmd_st.show_code = True # this is a "leaf" for errors
1939 i = self.arith_ev.EvalToBigInt(node.child)
1940 status = 1 if mops.Equal(i, mops.ZERO) else 0
1941 self._LeafTick()
1942
1943 elif case(command_e.ControlFlow): # LEAF command
1944 node = cast(command.ControlFlow, UP_node)
1945
1946 self.mem.SetTokenForLine(node.keyword)
1947 self._MaybeRunDebugTrap()
1948
1949 status = self._DoControlFlow(node)
1950 # Omit _LeafTick() since we likely raise an exception above
1951
1952 elif case(command_e.NoOp): # LEAF
1953 status = 0 # make it true
1954
1955 elif case(command_e.VarDecl): # YSH LEAF command
1956 node = cast(VarDecl, UP_node)
1957
1958 # Point to var name (bare assignment has no keyword)
1959 self.mem.SetTokenForLine(node.lhs[0].left)
1960 status = self._DoVarDecl(node)
1961 self._LeafTick()
1962
1963 elif case(command_e.Mutation): # YSH LEAF command
1964 node = cast(Mutation, UP_node)
1965
1966 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1967 self._DoMutation(node)
1968 status = 0 # if no exception is thrown, it succeeds
1969 self._LeafTick()
1970
1971 elif case(command_e.Expr): # YSH LEAF command
1972 node = cast(ExprCommand, UP_node)
1973
1974 self.mem.SetTokenForLine(node.keyword)
1975 # YSH debug trap?
1976
1977 status = self._DoExpr(node)
1978 self._LeafTick()
1979
1980 elif case(command_e.Retval): # YSH LEAF command
1981 node = cast(command.Retval, UP_node)
1982
1983 self.mem.SetTokenForLine(node.keyword)
1984 # YSH debug trap? I think we don't want the debug trap in func
1985 # dialect, for speed?
1986
1987 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1988 self._LeafTick()
1989
1990 raise vm.ValueControlFlow(node.keyword, val)
1991
1992 #
1993 # More commands that involve recursive calls
1994 #
1995
1996 elif case(command_e.ExpandedAlias):
1997 node = cast(command.ExpandedAlias, UP_node)
1998 status = self._DoExpandedAlias(node)
1999
2000 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
2001 # DoGroup has 'do' and 'done' spids for translation.
2002 elif case(command_e.CommandList):
2003 node = cast(command.CommandList, UP_node)
2004 status = self._ExecuteList(node.children)
2005
2006 elif case(command_e.DoGroup):
2007 node = cast(command.DoGroup, UP_node)
2008 status = self._ExecuteList(node.children)
2009
2010 elif case(command_e.BraceGroup):
2011 node = cast(BraceGroup, UP_node)
2012 status = self._ExecuteList(node.children)
2013
2014 elif case(command_e.AndOr):
2015 node = cast(command.AndOr, UP_node)
2016 status = self._DoAndOr(node, cmd_st)
2017
2018 elif case(command_e.If):
2019 node = cast(command.If, UP_node)
2020
2021 # Perf bug fix: loops might only execute 'if', but we still
2022 # need to GC
2023 self._LeafTick()
2024
2025 # No SetTokenForLine() because
2026 # - $LINENO can't appear directly in 'if'
2027 # - 'if' doesn't directly cause errors
2028 # It will be taken care of by command.Simple, condition, etc.
2029 status = self._DoIf(node)
2030
2031 elif case(command_e.Case):
2032 node = cast(command.Case, UP_node)
2033
2034 # Perf bug fix: loops might only execute 'case', but we still
2035 # need to GC
2036 self._LeafTick()
2037
2038 # Must set location for 'case $LINENO'
2039 self.mem.SetTokenForLine(node.case_kw)
2040 self._MaybeRunDebugTrap()
2041 status = self._DoCase(node)
2042
2043 elif case(command_e.WhileUntil):
2044 node = cast(command.WhileUntil, UP_node)
2045
2046 self.mem.SetTokenForLine(node.keyword)
2047 status = self._DoWhileUntil(node)
2048
2049 elif case(command_e.ForEach):
2050 node = cast(command.ForEach, UP_node)
2051
2052 self.mem.SetTokenForLine(node.keyword)
2053 status = self._DoForEach(node)
2054
2055 elif case(command_e.ForExpr):
2056 node = cast(command.ForExpr, UP_node)
2057
2058 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
2059 status = self._DoForExpr(node)
2060
2061 elif case(command_e.Redirect):
2062 node = cast(command.Redirect, UP_node)
2063
2064 # set -e affects redirect error, like mksh and bash 5.2, but unlike
2065 # dash/ash
2066 cmd_st.check_errexit = True
2067 status = self._DoRedirect(node, cmd_st)
2068
2069 elif case(command_e.Pipeline):
2070 node = cast(command.Pipeline, UP_node)
2071 status = self._DoPipeline(node, cmd_st)
2072
2073 elif case(command_e.Subshell):
2074 node = cast(command.Subshell, UP_node)
2075
2076 # This is a leaf from the parent process POV
2077 cmd_st.check_errexit = True
2078
2079 if node.is_last_cmd:
2080 # If the subshell is the last command in the process, just
2081 # run it in this process. See _MarkLastCommands().
2082 status = self._Execute(node.child)
2083 else:
2084 status = self.shell_ex.RunSubshell(node.child)
2085
2086 elif case(command_e.ShFunction):
2087 node = cast(ShFunction, UP_node)
2088 self._DoShFunction(node)
2089 status = 0
2090
2091 elif case(command_e.Proc):
2092 node = cast(Proc, UP_node)
2093 self._DoProc(node)
2094 status = 0
2095
2096 elif case(command_e.Func):
2097 node = cast(Func, UP_node)
2098
2099 # Needed for error, when the func is an existing variable name
2100 self.mem.SetTokenForLine(node.name)
2101
2102 self._DoFunc(node)
2103 status = 0
2104
2105 elif case(command_e.TimeBlock):
2106 node = cast(command.TimeBlock, UP_node)
2107 status = self._DoTimeBlock(node)
2108
2109 else:
2110 raise NotImplementedError(node.tag())
2111
2112 # Return to caller. Note the only case that didn't set it was Pipeline,
2113 # which set cmd_st.pipe_status.
2114 return status
2115
2116 def RunPendingTraps(self):
2117 # type: () -> None
2118
2119 trap_nodes = self.trap_state.GetPendingTraps()
2120 if trap_nodes is not None:
2121 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
2122 True):
2123 for trap_node in trap_nodes:
2124 with state.ctx_Registers(self.mem):
2125 # TODO: show trap kind in trace
2126 with dev.ctx_Tracer(self.tracer, 'trap', None):
2127 # Note: exit status is lost
2128 self._Execute(trap_node)
2129
2130 def RunPendingTrapsAndCatch(self):
2131 # type: () -> None
2132 """
2133 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
2134 """
2135 trap_nodes = self.trap_state.GetPendingTraps()
2136 if trap_nodes is not None:
2137 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
2138 True):
2139 for trap_node in trap_nodes:
2140 with state.ctx_Registers(self.mem):
2141 # TODO: show trap kind in trace
2142 with dev.ctx_Tracer(self.tracer, 'trap', None):
2143 # Note: exit status is lost
2144 try:
2145 self.ExecuteAndCatch(trap_node, 0)
2146 except util.UserExit:
2147 # If user calls 'exit', stop running traps, but
2148 # we still run the EXIT trap later.
2149 break
2150
2151 def _Execute(self, node):
2152 # type: (command_t) -> int
2153 """Call _Dispatch(), and perform the errexit check."""
2154
2155 # Optimization: These 2 records have rarely-used lists, so we don't pass
2156 # alloc_lists=True. We create them on demand.
2157 cmd_st = CommandStatus.CreateNull()
2158 if len(self.status_array_pool):
2159 # Optimized to avoid allocs
2160 process_sub_st = self.status_array_pool.pop()
2161 else:
2162 process_sub_st = StatusArray.CreateNull()
2163
2164 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
2165 try:
2166 status = self._Dispatch(node, cmd_st)
2167 except error.WordFailure as e: # e.g. echo hi > ${!undef}
2168 if not e.HasLocation(): # Last resort!
2169 e.location = self.mem.GetFallbackLocation()
2170 self.errfmt.PrettyPrintError(e)
2171 status = 1 # another redirect word eval error
2172 cmd_st.check_errexit = True # errexit for e.g. a=${!undef}
2173
2174 # Now we've waited for process subs
2175
2176 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
2177 # @_pipeline_status
2178 pipe_status = cmd_st.pipe_status
2179 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
2180 # makes it annoying to check both _process_sub_status and
2181 # _pipeline_status
2182
2183 errexit_loc = loc.Missing # type: loc_t
2184 if pipe_status is not None:
2185 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
2186 # for a REAL pipeline (but not singleton pipelines)
2187 assert status == -1, (
2188 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
2189 status)
2190
2191 self.mem.SetPipeStatus(pipe_status)
2192
2193 if self.exec_opts.pipefail():
2194 # The status is that of the last command that is non-zero.
2195 status = 0
2196 for i, st in enumerate(pipe_status):
2197 if st != 0:
2198 status = st
2199 errexit_loc = cmd_st.pipe_locs[i]
2200 else:
2201 # The status is that of last command, period.
2202 status = pipe_status[-1]
2203
2204 if cmd_st.pipe_negated:
2205 status = 1 if status == 0 else 0
2206
2207 # Compute status from _process_sub_status
2208 if process_sub_st.codes is None:
2209 # Optimized to avoid allocs
2210 self.status_array_pool.append(process_sub_st)
2211 else:
2212 codes = process_sub_st.codes
2213 self.mem.SetProcessSubStatus(codes)
2214 if status == 0 and self.exec_opts.process_sub_fail():
2215 # Choose the LAST non-zero status, consistent with pipefail above.
2216 for i, st in enumerate(codes):
2217 if st != 0:
2218 status = st
2219 errexit_loc = process_sub_st.locs[i]
2220
2221 self.mem.SetLastStatus(status)
2222
2223 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
2224 # However, any bash construct can appear in a pipeline. So it's easier
2225 # just to put it at the end, instead of after every node.
2226 #
2227 # Possible exceptions:
2228 # - function def (however this always exits 0 anyway)
2229 # - assignment - its result should be the result of the RHS?
2230 # - e.g. arith sub, command sub? I don't want arith sub.
2231 # - ControlFlow: always raises, it has no status.
2232 if cmd_st.check_errexit:
2233 #log('cmd_st %s', cmd_st)
2234 self._CheckStatus(status, cmd_st, node, errexit_loc)
2235
2236 return status
2237
2238 def _ExecuteList(self, children):
2239 # type: (List[command_t]) -> int
2240 status = 0 # for empty list
2241 for child in children:
2242 # last status wins
2243 status = self._Execute(child)
2244 return status
2245
2246 def LastStatus(self):
2247 # type: () -> int
2248 """For main_loop.py to determine the exit code of the shell itself."""
2249 return self.mem.LastStatus()
2250
2251 def _MarkLastCommands(self, node):
2252 # type: (command_t) -> None
2253
2254 if 0:
2255 log('optimizing')
2256 node.PrettyPrint(sys.stderr)
2257 log('')
2258
2259 UP_node = node
2260 with tagswitch(node) as case:
2261 if case(command_e.Simple):
2262 node = cast(command.Simple, UP_node)
2263 node.is_last_cmd = True
2264 if 0:
2265 log('Simple optimized')
2266
2267 elif case(command_e.Subshell):
2268 node = cast(command.Subshell, UP_node)
2269 # Mark ourselves as the last
2270 node.is_last_cmd = True
2271
2272 # Also mark 'date' as the last one
2273 # echo 1; (echo 2; date)
2274 self._MarkLastCommands(node.child)
2275
2276 elif case(command_e.Pipeline):
2277 node = cast(command.Pipeline, UP_node)
2278 # Bug fix: if we change the status, we can't exec the last
2279 # element!
2280 if node.negated is None and not self.exec_opts.pipefail():
2281 self._MarkLastCommands(node.children[-1])
2282
2283 elif case(command_e.Sentence):
2284 node = cast(command.Sentence, UP_node)
2285 self._MarkLastCommands(node.child)
2286
2287 elif case(command_e.Redirect):
2288 node = cast(command.Sentence, UP_node)
2289 # Don't need to restore the redirect in any of these cases:
2290
2291 # bin/osh -c 'echo hi 2>stderr'
2292 # bin/osh -c '{ echo hi; date; } 2>stderr'
2293 # echo hi 2>stderr | wc -l
2294
2295 self._MarkLastCommands(node.child)
2296
2297 elif case(command_e.CommandList):
2298 # Subshells often have a CommandList child
2299 node = cast(command.CommandList, UP_node)
2300 self._MarkLastCommands(node.children[-1])
2301
2302 elif case(command_e.BraceGroup):
2303 # TODO: What about redirects?
2304 node = cast(BraceGroup, UP_node)
2305 self._MarkLastCommands(node.children[-1])
2306
2307 def _RemoveSubshells(self, node):
2308 # type: (command_t) -> command_t
2309 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
2310
2311 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
2312 be correct otherwise.
2313 """
2314 UP_node = node
2315 with tagswitch(node) as case:
2316 if case(command_e.Subshell):
2317 node = cast(command.Subshell, UP_node)
2318 # Optimize ( ( date ) ) etc.
2319 return self._RemoveSubshells(node.child)
2320 return node
2321
2322 def ExecuteAndCatch(self, node, cmd_flags):
2323 # type: (command_t, int) -> Tuple[bool, bool]
2324 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2325
2326 Args:
2327 node: LST subtree
2328 optimize: Whether to exec the last process rather than fork/exec
2329
2330 Returns:
2331 TODO: use enum 'why' instead of the 2 booleans
2332
2333 Used by
2334 - main_loop.py.
2335 - SubProgramThunk for pipelines, subshell, command sub, process sub
2336 - TODO: Signals besides EXIT trap
2337
2338 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2339 finally_exit boolean. We use a different algorithm.
2340 """
2341 if cmd_flags & OptimizeSubshells:
2342 node = self._RemoveSubshells(node)
2343
2344 if cmd_flags & MarkLastCommands:
2345 # Mark the last command in each process, so we may avoid forks
2346 self._MarkLastCommands(node)
2347
2348 if 0:
2349 log('after opt:')
2350 node.PrettyPrint()
2351 log('')
2352
2353 is_return = False
2354 is_fatal = False
2355 is_errexit = False
2356
2357 err = None # type: error.FatalRuntime
2358 status = -1 # uninitialized
2359
2360 try:
2361 options = [] # type: List[int]
2362 if cmd_flags & NoDebugTrap:
2363 options.append(option_i._no_debug_trap)
2364 if cmd_flags & NoErrTrap:
2365 options.append(option_i._no_err_trap)
2366 with state.ctx_Option(self.mutable_opts, options, True):
2367 status = self._Execute(node)
2368 except vm.IntControlFlow as e:
2369 if cmd_flags & RaiseControlFlow:
2370 raise # 'eval break' and 'source return.sh', etc.
2371 else:
2372 # Return at top level is OK, unlike in bash.
2373 if e.IsReturn():
2374 is_return = True
2375 status = e.StatusCode()
2376 else:
2377 # TODO: This error message is invalid. It can also happen
2378 # in eval. Perhaps we need a flag.
2379
2380 # Invalid control flow
2381 self.errfmt.Print_(
2382 "Loop and control flow can't be in different processes",
2383 blame_loc=e.Location())
2384 is_fatal = True
2385 # All shells exit 0 here. It could be hidden behind
2386 # strict_control_flow if the incompatibility causes problems.
2387 status = 1
2388 except error.Parse as e:
2389 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2390 raise
2391 except error.ErrExit as e:
2392 err = e
2393 is_errexit = True
2394 except error.FatalRuntime as e:
2395 err = e
2396
2397 if err:
2398 status = err.ExitStatus()
2399
2400 is_fatal = True
2401 # Do this before unwinding stack
2402 self.dumper.MaybeRecord(self, err)
2403
2404 if not err.HasLocation(): # Last resort!
2405 #log('Missing location')
2406 err.location = self.mem.GetFallbackLocation()
2407 #log('%s', err.location)
2408
2409 if is_errexit:
2410 if self.exec_opts.verbose_errexit():
2411 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2412 posix.getpid())
2413 else:
2414 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2415
2416 assert status >= 0, 'Should have been initialized'
2417
2418 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2419 # created a crash dump. So we get 2 or more of them.
2420 self.dumper.MaybeDump(status)
2421
2422 self.mem.SetLastStatus(status)
2423 return is_return, is_fatal
2424
2425 def EvalCommandFrag(self, frag):
2426 # type: (command_t) -> int
2427 """For builtins to evaluate command args.
2428
2429 Many exceptions are raised.
2430
2431 Examples:
2432
2433 cd /tmp (; ; mycmd)
2434
2435 And:
2436 eval (mycmd)
2437 call _io->eval(mycmd)
2438
2439 (Should those be more like eval 'mystring'?)
2440 """
2441 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2442
2443 if 0:
2444
2445 def EvalCommandClosure(self, cmd):
2446 # type: (value.Command) -> int
2447 frag = typed_args.GetCommandFrag(cmd)
2448 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2449 cmd.module_frame):
2450 return self.EvalCommandFrag(frag)
2451
2452 def RunTrapsOnExit(self, mut_status):
2453 # type: (IntParamBox) -> None
2454 """If an EXIT trap handler exists, run it.
2455
2456 It only mutates the status if 'return' or 'exit'. This is odd
2457 behavior, but bash/dash/mksh seem to agree on it. See test cases in
2458 builtin-trap.test.sh.
2459 """
2460 # This does not raise, even on 'exit', etc.
2461 self.RunPendingTrapsAndCatch()
2462
2463 node = self.trap_state.GetHook('EXIT') # type: command_t
2464 if node:
2465 # Note: Don't set option_i._running_trap, because that's for
2466 # RunPendingTraps() in the MAIN LOOP
2467
2468 # Note: we're not using ctx_EnclosedFrame(), so
2469 # proc p {
2470 # var x = 'local'
2471 # trap --add { echo $x }
2472 # }
2473 # doesn't capture x. This is documented in doc/ref/
2474
2475 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2476 try:
2477 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2478 except util.UserExit as e: # explicit exit
2479 mut_status.i = e.status
2480 return
2481 if is_return: # explicit 'return' in the trap handler!
2482 mut_status.i = self.LastStatus()
2483
2484 def _MaybeRunDebugTrap(self):
2485 # type: () -> None
2486 """Run user-specified DEBUG code before certain commands."""
2487 node = self.trap_state.GetHook('DEBUG') # type: command_t
2488 if node is None:
2489 return
2490
2491 # Fix lastpipe / job control / DEBUG trap interaction
2492 if self.exec_opts._no_debug_trap():
2493 return
2494
2495 # Don't run recursively run traps, etc.
2496 if not self.mem.ShouldRunDebugTrap():
2497 return
2498
2499 # NOTE: Don't set option_i._running_trap, because that's for
2500 # RunPendingTraps() in the MAIN LOOP
2501
2502 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2503 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2504 # for SetTokenForLine $LINENO
2505 with state.ctx_DebugTrap(self.mem):
2506 # Don't catch util.UserExit, etc.
2507 self._Execute(node)
2508
2509 def _MaybeRunErrTrap(self):
2510 # type: () -> None
2511 """
2512 Run user-specified ERR code after checking the status of certain
2513 commands (pipelines)
2514 """
2515 node = self.trap_state.GetHook('ERR') # type: command_t
2516 if node is None:
2517 return
2518
2519 # ERR trap is only run for a whole pipeline, not its parts
2520 if self.exec_opts._no_err_trap():
2521 return
2522
2523 # Prevent infinite recursion
2524 if self.mem.running_err_trap:
2525 return
2526
2527 # "disabled errexit" rule
2528 if self.mutable_opts.ErrExitIsDisabled():
2529 return
2530
2531 # bash rule - affected by set -o errtrace
2532 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2533 return
2534
2535 # NOTE: Don't set option_i._running_trap, because that's for
2536 # RunPendingTraps() in the MAIN LOOP
2537
2538 # To make a better stack trace from vm.getDebugStack(), add the last
2539 # thing that failed, even if it's not a proc/func. This can be an
2540 # external command.
2541 #
2542 # TODO: this can lead to duplicate stack frames
2543 # - We might only want this if it's an external command?
2544 # - Or maybe we need a different trap to trigger stack traces ...
2545 self.mem.debug_stack.append(
2546 debug_frame.BeforeErrTrap(self.mem.token_for_line))
2547
2548 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2549 # TODO:
2550 # - use debug_frame.Trap
2551 # - use the original location of the 'trap' command?
2552 # - combine ctx_Tracer and debug stack? They are similar
2553 #with state.ctx_EvalDebugFrame(self.mem, self.mem.token_for_line):
2554
2555 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2556 # So unlike other traps, we don't isolate registers.
2557 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2558
2559 with state.ctx_ErrTrap(self.mem):
2560 self._Execute(node)
2561
2562 def RunProc(self, proc, cmd_val):
2563 # type: (value.Proc, cmd_value.Argv) -> int
2564 """Run procs aka "shell functions".
2565
2566 For SimpleCommand and registered completion hooks.
2567 """
2568 sig = proc.sig
2569 if sig.tag() == proc_sig_e.Closed:
2570 # We're binding named params. User should use @rest. No 'shift'.
2571 proc_argv = [] # type: List[str]
2572 else:
2573 proc_argv = cmd_val.argv[1:]
2574
2575 # Hm this sets "$@". TODO: Set ARGV only
2576 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv,
2577 cmd_val.arg_locs[0]):
2578 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2579
2580 # Redirects still valid for functions.
2581 # Here doc causes a pipe and Process(SubProgramThunk).
2582 try:
2583 status = self._Execute(proc.body)
2584 except vm.IntControlFlow as e:
2585 if e.IsReturn():
2586 status = e.StatusCode()
2587 else:
2588 # break/continue used in the wrong place.
2589 e_die('Unexpected %r (in proc call)' % e.Keyword(),
2590 e.Location())
2591 except error.FatalRuntime as e:
2592 # Dump the stack before unwinding it
2593 self.dumper.MaybeRecord(self, e)
2594 raise
2595
2596 return status
2597
2598 def RunFuncForCompletion(self, proc, argv):
2599 # type: (value.Proc, List[str]) -> int
2600 """
2601 Args:
2602 argv: $1 $2 $3 ... not including $0
2603 """
2604 cmd_val = MakeBuiltinArgv(argv)
2605
2606 # TODO: Change this to run YSH procs and funcs too
2607 try:
2608 status = self.RunProc(proc, cmd_val)
2609 except error.FatalRuntime as e:
2610 self.errfmt.PrettyPrintError(e)
2611 status = e.ExitStatus()
2612 except vm.IntControlFlow as e:
2613 # shouldn't be able to exit the shell from a completion hook!
2614 # TODO: Avoid overwriting the prompt!
2615 self.errfmt.Print_('Attempted to exit from completion hook.',
2616 blame_loc=e.Location())
2617
2618 status = 1
2619 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2620 return status
2621
2622
2623# vim: sw=4