def RunCommandSub(self, node): # type: (command_t) -> str # Hack for weird $(<file) construct if node.tag_() == command_e.Simple: simple = cast(command__Simple, node) # Detect '< file' if (len(simple.words) == 0 and len(simple.redirects) == 1 and simple.redirects[0].op.id == Id.Redir_Less): # change it to __cat < file # note: cmd_eval.py _Dispatch works around lack of spid tok = Token(Id.Lit_Chars, runtime.NO_SPID, '__cat') cat_word = compound_word([tok]) # MUTATE the command.Simple node. This will only be done the first # time in the parent process. simple.words.append(cat_word) p = self._MakeProcess(node, inherit_errexit=self.exec_opts.inherit_errexit()) r, w = posix.pipe() p.AddStateChange(process.StdoutToPipe(r, w)) _ = p.Start() #log('Command sub started %d', pid) chunks = [] # type: List[str] posix.close(w) # not going to write while True: byte_str = posix.read(r, 4096) if len(byte_str) == 0: break chunks.append(byte_str) posix.close(r) status = p.Wait(self.waiter) # OSH has the concept of aborting in the middle of a WORD. We're not # waiting until the command is over! if self.exec_opts.more_errexit(): if self.exec_opts.errexit() and status != 0: raise error.ErrExit('Command sub exited with status %d (%r)', status, NewStr(command_str(node.tag_()))) else: # Set a flag so we check errexit at the same time as bash. Example: # # a=$(false) # echo foo # no matter what comes here, the flag is reset # # Set ONLY until this command node has finished executing. # HACK: move this self.cmd_ev.check_command_sub_status = True self.mem.SetLastStatus(status) # Runtime errors test case: # $("echo foo > $@") # Why rstrip()? # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char return ''.join(chunks).rstrip('\n')
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # TODO: Also hard usage error here too? attrs, arg_r = flag_spec.ParseOilCmdVal('run', cmd_val) arg = arg_types.run(attrs.attrs) if arg_r.Peek() is None: # HARD ERROR, not e_usage(), because errexit is often disabled! e_die("'run' expected a command to run", status=2) argv, spids = arg_r.Rest2() cmd_val2 = cmd_value.Argv(argv, spids, cmd_val.block) # Set in the 'except' block, e.g. if 'myfunc' failed failure_spid = runtime.NO_SPID try: # Temporarily turn ON errexit, and blame the 'run' spid. Note that # 'if run myproc' disables it and then enables it! with state.ctx_ErrExit(self.mutable_opts, True, cmd_val.arg_spids[0]): # Pass do_fork=True. Slight annoyance: the real value is a field of # command.Simple(). See _NoForkLast() in CommandEvaluator We have an # extra fork (miss out on an optimization) of code like ( status ls ) # or forkwait { status ls }, but that is NOT idiomatic code. status is # for functions. status = self.shell_ex.RunSimpleCommand(cmd_val2, True) #log('st %d', status) except error.ErrExit as e: # from functino call #log('e %d', e.exit_status) status = e.exit_status failure_spid = e.span_id # Do this before -allow-status-01 if arg.status_ok is not None: status = _AdjustStatus(arg.status_ok, status) if arg.allow_status_01 and status not in (0, 1): if failure_spid != runtime.NO_SPID: self.errfmt.Print_('(original failure)', span_id=failure_spid) self.errfmt.StderrLine('') raise error.ErrExit('fatal: status %d when --allow-status-01' % status, span_id=spids[0], status=status) if arg.assign_status is not None: var_name = arg.assign_status if var_name.startswith(':'): var_name = var_name[1:] state.SetRefString(self.mem, var_name, str(status)) return 0 # don't fail return status
def RunCommandSub(self, cs_part): # type: (command_sub) -> str if not self.exec_opts.allow_command_sub(): # TODO: # - Add spid of $( # - Better hints. Use 'run' for 'if myfunc', and 2 lines like local x; # x=$(false) fo assignment builtins. # - Maybe we should have an error message ID that links somewhere? e_die( "Command subs not allowed here because status wouldn't be checked (strict_errexit)." ) node = cs_part.child # Hack for weird $(<file) construct if node.tag_() == command_e.Simple: simple = cast(command__Simple, node) # Detect '< file' if (len(simple.words) == 0 and len(simple.redirects) == 1 and simple.redirects[0].op.id == Id.Redir_Less): # change it to __cat < file # note: cmd_eval.py _Dispatch works around lack of spid tok = Token(Id.Lit_Chars, runtime.NO_SPID, '__cat') cat_word = compound_word([tok]) # MUTATE the command.Simple node. This will only be done the first # time in the parent process. simple.words.append(cat_word) p = self._MakeProcess(node, inherit_errexit=self.exec_opts.inherit_errexit()) r, w = posix.pipe() p.AddStateChange(process.StdoutToPipe(r, w)) _ = p.Start() #log('Command sub started %d', pid) chunks = [] # type: List[str] posix.close(w) # not going to write while True: byte_str = posix.read(r, 4096) if len(byte_str) == 0: break chunks.append(byte_str) posix.close(r) status = p.Wait(self.waiter) # OSH has the concept of aborting in the middle of a WORD. We're not # waiting until the command is over! if self.exec_opts.command_sub_errexit(): if status != 0: raise error.ErrExit('Command sub exited with status %d (%s)' % (status, ui.CommandType(node)), span_id=cs_part.left_token.span_id, status=status) else: # Set a flag so we check errexit at the same time as bash. Example: # # a=$(false) # echo foo # no matter what comes here, the flag is reset # # Set ONLY until this command node has finished executing. # HACK: move this self.cmd_ev.check_command_sub_status = True self.mem.SetLastStatus(status) # Runtime errors test case: # $("echo foo > $@") # Why rstrip()? # https://unix.stackexchange.com/questions/17747/why-does-shell-command-substitution-gobble-up-a-trailing-newline-char return ''.join(chunks).rstrip('\n')