OILS / osh / cmd_eval.py View on Github | oilshell.org

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