def _PushDup(self, fd1, loc): # type: (int, redir_loc_t) -> int """Save fd2 in a higher range, and dup fd1 onto fd2. Returns whether F_DUPFD/dup2 succeeded, and the new descriptor. """ UP_loc = loc if loc.tag_() == redir_loc_e.VarName: fd2_name = cast(redir_loc__VarName, UP_loc).name try: # F_DUPFD: GREATER than range new_fd = fcntl.fcntl(fd1, fcntl.F_DUPFD, _SHELL_MIN_FD) # type: int except IOError as e: if e.errno == errno_.EBADF: self.errfmt.Print_('%d: %s' % (fd1, pyutil.strerror_IO(e))) return NO_FD else: raise # this redirect failed self._WriteFdToMem(fd2_name, new_fd) elif loc.tag_() == redir_loc_e.Fd: fd2 = cast(redir_loc__Fd, UP_loc).fd if fd1 == fd2: # The user could have asked for it to be open on descrptor 3, but open() # already returned 3, e.g. echo 3>out.txt return NO_FD # Check the validity of fd1 before _PushSave(fd2) try: fcntl.fcntl(fd1, fcntl.F_GETFD) except IOError as e: self.errfmt.Print_('%d: %s' % (fd1, pyutil.strerror_IO(e))) raise need_restore = self._PushSave(fd2) #log('==== dup2 %s %s\n' % (fd1, fd2)) try: posix.dup2(fd1, fd2) except OSError as e: # bash/dash give this error too, e.g. for 'echo hi 1>&3' self.errfmt.Print_('%d: %s' % (fd1, pyutil.strerror_OS(e))) # Restore and return error if need_restore: new_fd, fd2, _ = self.cur_frame.saved.pop() posix.dup2(new_fd, fd2) posix.close(new_fd) raise # this redirect failed new_fd = fd2 else: raise AssertionError() return new_fd
def Run(self): # type: () -> None # NOTE: may NOT return due to exec(). if not self.inherit_errexit: self.cmd_ev.mutable_opts.errexit.Disable() try: # optimize to eliminate redundant subshells like ( echo hi ) | wc -l etc. self.cmd_ev.ExecuteAndCatch(self.node, cmd_flags=cmd_eval.Optimize) status = self.cmd_ev.LastStatus() # NOTE: We ignore the is_fatal return value. The user should set -o # errexit so failures in subprocesses cause failures in the parent. except util.UserExit as e: status = e.status # Handle errors in a subshell. These two cases are repeated from main() # and the core/completion.py hook. except KeyboardInterrupt: print('') status = 130 # 128 + 2 except IOError as e: stderr_line('osh I/O error: %s', pyutil.strerror_IO(e)) status = 2 except OSError as e: # mycpp: duplicated for translation stderr_line('osh I/O error: %s', pyutil.strerror_OS(e)) status = 2 # Raises SystemExit, so we still have time to write a crash dump. exit(status)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int num_args = len(cmd_val.argv) - 1 if num_args == 0: # TODO: It's suppose to try another dir before doing this? self.errfmt.Print_('pushd: no other directory') return 1 elif num_args > 1: e_usage('got too many arguments') # TODO: 'cd' uses normpath? Is that inconsistent? dest_dir = os_path.abspath(cmd_val.argv[1]) try: posix.chdir(dest_dir) except OSError as e: self.errfmt.Print_("pushd: %r: %s" % (dest_dir, pyutil.strerror_OS(e)), span_id=cmd_val.arg_spids[1]) return 1 self.dir_stack.Push(dest_dir) _PrintDirStack(self.dir_stack, SINGLE_LINE, state.MaybeString(self.mem, 'HOME')) state.ExportGlobalString(self.mem, 'PWD', dest_dir) self.mem.SetPwd(dest_dir) return 0
def _Exec(self, argv0_path, argv, argv0_spid, environ, should_retry): # type: (str, List[str], int, Dict[str, str], bool) -> None if len(self.hijack_shebang): try: f = self.fd_state.Open(argv0_path) except OSError as e: pass else: try: # Test if the shebang looks like a shell. The file might be binary # with no newlines, so read 80 bytes instead of readline(). line = f.read(80) # type: ignore # TODO: fix this if match.ShouldHijack(line): argv = [self.hijack_shebang, argv0_path] + argv[1:] argv0_path = self.hijack_shebang self.debug_f.log('Hijacked: %s', argv) else: #self.debug_f.log('Not hijacking %s (%r)', argv, line) pass finally: f.close() # TODO: If there is an error, like the file isn't executable, then we should # exit, and the parent will reap it. Should it capture stderr? try: posix.execve(argv0_path, argv, environ) except OSError as e: # Run with /bin/sh when ENOEXEC error (no shebang). All shells do this. if e.errno == errno_.ENOEXEC and should_retry: new_argv = ['/bin/sh', argv0_path] new_argv.extend(argv[1:]) self._Exec('/bin/sh', new_argv, argv0_spid, environ, False) # NO RETURN # Would be nice: when the path is relative and ENOENT: print PWD and do # spelling correction? self.errfmt.Print_("Can't execute %r: %s" % (argv0_path, pyutil.strerror_OS(e)), span_id=argv0_spid) # POSIX mentions 126 and 127 for two specific errors. The rest are # unspecified. # # http://pubs.opengroup.org/onlinepubs/9699919799.2016edition/utilities/V3_chap02.html#tag_18_08_02 if e.errno == errno_.EACCES: status = 126 elif e.errno == errno_.ENOENT: # TODO: most shells print 'command not found', rather than strerror() # == "No such file or directory". That's better because it's at the # end of the path search, and we're never searching for a directory. status = 127 else: # dash uses 2, but we use that for parse errors. This seems to be # consistent with mksh and zsh. status = 127 exit(status) # raises SystemExit
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv call_spid = cmd_val.arg_spids[0] try: path = argv[1] except IndexError: raise error.Usage('missing required argument') resolved = self.search_path.Lookup(path, exec_required=False) if resolved is None: resolved = path try: f = self.fd_state.Open(resolved) # Shell can't use descriptors 3-9 except OSError as e: self.errfmt.Print('source %r failed: %s', path, pyutil.strerror_OS(e), span_id=cmd_val.arg_spids[1]) return 1 try: line_reader = reader.FileLineReader(f, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # A sourced module CAN have a new arguments array, but it always shares # the same variable scope as the caller. The caller could be at either a # global or a local scope. source_argv = argv[2:] self.mem.PushSource(path, source_argv) src = source.SourcedFile(path, call_spid) self.arena.PushSource(src) try: status = main_loop.Batch(self.cmd_ev, c_parser, self.arena) finally: self.arena.PopSource() self.mem.PopSource(source_argv) return status except _ControlFlow as e: if e.IsReturn(): return e.StatusCode() else: raise finally: f.close()
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int call_spid = cmd_val.arg_spids[0] _, arg_r = flag_spec.ParseCmdVal('source', cmd_val) path = arg_r.Peek() if path is None: e_usage('missing required argument') arg_r.Next() resolved = self.search_path.Lookup(path, exec_required=False) if resolved is None: resolved = path try: f = self.fd_state.Open(resolved) # Shell can't use descriptors 3-9 except OSError as e: self.errfmt.Print_('source %r failed: %s' % (path, pyutil.strerror_OS(e)), span_id=cmd_val.arg_spids[1]) return 1 try: line_reader = reader.FileLineReader(f, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) # A sourced module CAN have a new arguments array, but it always shares # the same variable scope as the caller. The caller could be at either a # global or a local scope. source_argv = arg_r.Rest() self.mem.PushSource(path, source_argv) src = source.SourcedFile(path, call_spid) try: with alloc.ctx_Location(self.arena, src): status = main_loop.Batch(self.cmd_ev, c_parser, self.arena, cmd_flags=cmd_eval.IsEvalSource) finally: self.mem.PopSource(source_argv) return status except _ControlFlow as e: if e.IsReturn(): return e.StatusCode() else: raise finally: f.close()
def _PopDirStack(mem, dir_stack, errfmt): # type: (Mem, DirStack, ErrorFormatter) -> bool """Helper for popd and cd { ... }.""" dest_dir = dir_stack.Pop() if dest_dir is None: errfmt.Print_('popd: directory stack is empty') return False try: posix.chdir(dest_dir) except OSError as e: # Happens if a directory is deleted in pushing and popping errfmt.Print_("popd: %r: %s" % (dest_dir, pyutil.strerror_OS(e))) return False state.SetGlobalString(mem, 'PWD', dest_dir) mem.SetPwd(dest_dir) return True
def main(argv): # type: (List[str]) -> int loader = pyutil.GetResourceLoader() login_shell = False environ = {} # type: Dict[str, str] environ['PWD'] = posix.getcwd() arg_r = args.Reader(argv, spids=[runtime.NO_SPID] * len(argv)) try: status = pure.Main('osh', arg_r, environ, login_shell, loader, None) return status except error.Usage as e: #builtin.Help(['oil-usage'], util.GetResourceLoader()) log('oil: %s', e.msg) return 2 except RuntimeError as e: if 0: import traceback traceback.print_exc() # NOTE: The Python interpreter can cause this, e.g. on stack overflow. # f() { f; }; f will cause this msg = e.message # type: str stderr_line('osh fatal error: %s', msg) return 1 # Note: This doesn't happen in C++. except KeyboardInterrupt: print('') return 130 # 128 + 2 except OSError as e: if 0: import traceback traceback.print_exc() # test this with prlimit --nproc=1 --pid=$$ stderr_line('osh I/O error: %s', pyutil.strerror_OS(e)) return 2 # dash gives status 2 except IOError as e: # duplicate of above because CPython is inconsistent stderr_line('osh I/O error: %s', pyutil.strerror_IO(e)) return 2
def _ApplyRedirect(self, r, waiter): # type: (redirect, Waiter) -> None arg = r.arg UP_arg = arg with tagswitch(arg) as case: if case(redirect_arg_e.Path): arg = cast(redirect_arg__Path, UP_arg) if r.op_id in (Id.Redir_Great, Id.Redir_AndGreat): # > &> # NOTE: This is different than >| because it respects noclobber, but # that option is almost never used. See test/wild.sh. mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id == Id.Redir_Clobber: # >| mode = posix.O_CREAT | posix.O_WRONLY | posix.O_TRUNC elif r.op_id in (Id.Redir_DGreat, Id.Redir_AndDGreat): # >> &>> mode = posix.O_CREAT | posix.O_WRONLY | posix.O_APPEND elif r.op_id == Id.Redir_Less: # < mode = posix.O_RDONLY elif r.op_id == Id.Redir_LessGreat: # <> mode = posix.O_CREAT | posix.O_RDWR else: raise NotImplementedError(r.op_id) # NOTE: 0666 is affected by umask, all shells use it. try: open_fd = posix.open(arg.filename, mode, 0o666) except OSError as e: self.errfmt.Print_("Can't open %r: %s" % (arg.filename, pyutil.strerror_OS(e)), span_id=r.op_spid) raise # redirect failed new_fd = self._PushDup(open_fd, r.loc) if new_fd != NO_FD: posix.close(open_fd) # Now handle &> and &>> and their variants. These pairs are the same: # # stdout_stderr.py &> out-err.txt # stdout_stderr.py > out-err.txt 2>&1 # # stdout_stderr.py 3&> out-err.txt # stdout_stderr.py 3> out-err.txt 2>&3 # # Ditto for {fd}> and {fd}&> if r.op_id in (Id.Redir_AndGreat, Id.Redir_AndDGreat): self._PushDup(new_fd, redir_loc.Fd(2)) elif case(redirect_arg_e.CopyFd): # e.g. echo hi 1>&2 arg = cast(redirect_arg__CopyFd, UP_arg) if r.op_id == Id.Redir_GreatAnd: # 1>&2 self._PushDup(arg.target_fd, r.loc) elif r.op_id == Id.Redir_LessAnd: # 0<&5 # The only difference between >& and <& is the default file # descriptor argument. self._PushDup(arg.target_fd, r.loc) else: raise NotImplementedError() elif case(redirect_arg_e.MoveFd): # e.g. echo hi 5>&6- arg = cast(redirect_arg__MoveFd, UP_arg) new_fd = self._PushDup(arg.target_fd, r.loc) if new_fd != NO_FD: posix.close(arg.target_fd) UP_loc = r.loc if r.loc.tag_() == redir_loc_e.Fd: fd = cast(redir_loc__Fd, UP_loc).fd else: fd = NO_FD self.cur_frame.saved.append((new_fd, fd, False)) elif case(redirect_arg_e.CloseFd): # e.g. echo hi 5>&- self._PushCloseFd(r.loc) elif case(redirect_arg_e.HereDoc): arg = cast(redirect_arg__HereDoc, UP_arg) # NOTE: Do these descriptors have to be moved out of the range 0-9? read_fd, write_fd = posix.pipe() self._PushDup(read_fd, r.loc) # stdin is now the pipe # We can't close like we do in the filename case above? The writer can # get a "broken pipe". self._PushClose(read_fd) thunk = _HereDocWriterThunk(write_fd, arg.body) # TODO: Use PIPE_SIZE to save a process in the case of small here docs, # which are the common case. (dash does this.) start_process = True #start_process = False if start_process: here_proc = Process(thunk, self.job_state) # NOTE: we could close the read pipe here, but it doesn't really # matter because we control the code. _ = here_proc.Start() #log('Started %s as %d', here_proc, pid) self._PushWait(here_proc, waiter) # Now that we've started the child, close it in the parent. posix.close(write_fd) else: posix.write(write_fd, arg.body) posix.close(write_fd)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('cd', cmd_val) arg = arg_types.cd(attrs.attrs) dest_dir, arg_spid = arg_r.Peek2() if dest_dir is None: val = self.mem.GetVar('HOME') try: dest_dir = state.GetString(self.mem, 'HOME') except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 if dest_dir == '-': try: dest_dir = state.GetString(self.mem, 'OLDPWD') print(dest_dir) # Shells print the directory except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 try: pwd = state.GetString(self.mem, 'PWD') except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 # Calculate new directory, chdir() to it, then set PWD to it. NOTE: We can't # call posix.getcwd() because it can raise OSError if the directory was # removed (ENOENT.) abspath = os_path.join(pwd, dest_dir) # make it absolute, for cd .. if arg.P: # -P means resolve symbolic links, then process '..' real_dest_dir = libc.realpath(abspath) else: # -L means process '..' first. This just does string manipulation. (But # realpath afterward isn't correct?) real_dest_dir = os_path.normpath(abspath) try: posix.chdir(real_dest_dir) except OSError as e: self.errfmt.Print_("cd %r: %s" % (real_dest_dir, pyutil.strerror_OS(e)), span_id=arg_spid) return 1 state.ExportGlobalString(self.mem, 'PWD', real_dest_dir) # WEIRD: We need a copy that is NOT PWD, because the user could mutate PWD. # Other shells use global variables. self.mem.SetPwd(real_dest_dir) if cmd_val.block: self.dir_stack.Push(real_dest_dir) try: unused = self.cmd_ev.EvalBlock(cmd_val.block) finally: # TODO: Change this to a context manager. # note: it might be more consistent to use an exception here. if not _PopDirStack(self.mem, self.dir_stack, self.errfmt): return 1 else: # No block state.ExportGlobalString(self.mem, 'OLDPWD', pwd) self.dir_stack.Reset() # for pushd/popd/dirs return 0
def Main(lang, arg_r, environ, login_shell, loader, line_input): # type: (str, args.Reader, Dict[str, str], bool, pyutil._ResourceLoader, Any) -> int """The full shell lifecycle. Used by bin/osh and bin/oil. Args: lang: 'osh' or 'oil' argv0, arg_r: command line arguments environ: environment login_shell: Was - on the front? loader: to get help, version, grammar, etc. line_input: optional GNU readline """ # Differences between osh and oil: # - --help? I guess Oil has a SUPERSET of OSH options. # - oshrc vs oilrc # - shopt -s oil:all # - Change the prompt in the interactive shell? # osh-pure: # - no oil grammar # - no expression evaluator # - no interactive shell, or line_input # - no process.* # process.{ExternalProgram,Waiter,FdState,JobState,SignalState} -- we want # to evaluate config files without any of these # Modules not translated yet: completion, comp_ui, builtin_comp, process # - word evaluator # - shouldn't glob? set -o noglob? or hard failure? # - ~ shouldn't read from the file system # - I guess it can just be the HOME=HOME? # Builtin: # shellvm -c 'echo hi' # shellvm <<< 'echo hi' argv0 = arg_r.Peek() assert argv0 is not None arg_r.Next() assert lang in ('osh', 'oil'), lang try: attrs = flag_spec.ParseMore('main', arg_r) except error.Usage as e: stderr_line('osh usage error: %s', e.msg) return 2 flag = arg_types.main(attrs.attrs) arena = alloc.Arena() errfmt = ui.ErrorFormatter(arena) help_builtin = builtin_misc.Help(loader, errfmt) if flag.help: help_builtin.Run(MakeBuiltinArgv(['%s-usage' % lang])) return 0 if flag.version: # OSH version is the only binary in Oil right now, so it's all one version. pyutil.ShowAppVersion('Oil', loader) return 0 no_str = None # type: str debug_stack = [] # type: List[state.DebugFrame] if arg_r.AtEnd(): dollar0 = argv0 else: dollar0 = arg_r.Peek() # the script name, or the arg after -c # Copy quirky bash behavior. frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0, 0) debug_stack.append(frame0) # Copy quirky bash behavior. frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0) debug_stack.append(frame1) script_name = arg_r.Peek() # type: Optional[str] arg_r.Next() mem = state.Mem(dollar0, arg_r.Rest(), arena, debug_stack) version_str = pyutil.GetVersion(loader) state.InitMem(mem, environ, version_str) procs = {} # type: Dict[str, command__ShFunction] #job_state = process.JobState() #fd_state = process.FdState(errfmt, job_state, mem) opt_hook = state.OptHook() parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # TODO: only MutableOpts needs mem, so it's not a true circular dep. mem.exec_opts = exec_opts # circular dep if attrs.show_options: # special case: sh -o mutable_opts.ShowOptions([]) return 0 # Set these BEFORE processing flags, so they can be overridden. if lang == 'oil': mutable_opts.SetShoptOption('oil:all', True) builtin_pure.SetShellOpts(mutable_opts, attrs.opt_changes, attrs.shopt_changes) # feedback between runtime and parser aliases = {} # type: Dict[str, str] oil_grammar = None # type: grammar.Grammar #oil_grammar = meta.LoadOilGrammar(loader) if flag.one_pass_parse and not exec_opts.noexec(): e_usage('--one-pass-parse requires noexec (-n)') parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) parse_ctx.Init_OnePassParse(flag.one_pass_parse) # Three ParseContext instances SHARE aliases. comp_arena = alloc.Arena() comp_arena.PushSource(source.Unused('completion')) trail1 = parse_lib.Trail() # one_pass_parse needs to be turned on to complete inside backticks. TODO: # fix the issue where ` gets erased because it's not part of # set_completer_delims(). comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases, oil_grammar) comp_ctx.Init_Trail(trail1) comp_ctx.Init_OnePassParse(True) hist_arena = alloc.Arena() hist_arena.PushSource(source.Unused('history')) trail2 = parse_lib.Trail() hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases, oil_grammar) hist_ctx.Init_Trail(trail2) # Deps helps manages dependencies. These dependencies are circular: # - cmd_ev and word_ev, arith_ev -- for command sub, arith sub # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 )) # - cmd_ev and builtins (which execute code, like eval) # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P cmd_deps = cmd_eval.Deps() cmd_deps.mutable_opts = mutable_opts # TODO: In general, cmd_deps are shared between the mutually recursive # evaluators. Some of the four below are only shared between a builtin and # the CommandEvaluator, so we could put them somewhere else. cmd_deps.traps = {} cmd_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates #waiter = process.Waiter(job_state, exec_opts) my_pid = posix.getpid() debug_path = '' debug_dir = environ.get('OSH_DEBUG_DIR') if flag.debug_file: # --debug-file takes precedence over OSH_DEBUG_DIR debug_path = flag.debug_file elif debug_dir is not None: debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid) if len(debug_path): raise NotImplementedError() else: debug_f = util.NullDebugFile() # type: util._DebugFile cmd_deps.debug_f = debug_f # Not using datetime for dependency reasons. TODO: maybe show the date at # the beginning of the log, and then only show time afterward? To save # space, and make space for microseconds. (datetime supports microseconds # but time.strftime doesn't). if mylib.PYTHON: iso_stamp = time_.strftime("%Y-%m-%d %H:%M:%S") debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, arg_r.argv) if len(debug_path): debug_f.log('Writing logs to %r', debug_path) interp = environ.get('OSH_HIJACK_SHEBANG', '') search_path = state.SearchPath(mem) #ext_prog = process.ExternalProgram(interp, fd_state, errfmt, debug_f) splitter = split.SplitContext(mem) # This could just be OSH_DEBUG_STREAMS='debug crash' ? That might be # stuffing too much into one, since a .json crash dump isn't a stream. crash_dump_dir = environ.get('OSH_CRASH_DUMP_DIR', '') cmd_deps.dumper = dev.CrashDumper(crash_dump_dir) if flag.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(mylib.Stderr()) #comp_lookup = completion.Lookup() # Various Global State objects to work around readline interfaces #compopt_state = completion.OptionState() #comp_ui_state = comp_ui.State() #prompt_state = comp_ui.PromptState() dir_stack = state.DirStack() # # Initialize builtins that don't depend on evaluators # builtins = {} # type: Dict[int, vm._Builtin] AddPure(builtins, mem, procs, mutable_opts, aliases, search_path, errfmt) AddIO(builtins, mem, dir_stack, exec_opts, splitter, errfmt) builtins[builtin_i.help] = help_builtin # # Assignment builtins # assign_b = {} # type: Dict[int, vm._AssignBuiltin] new_var = builtin_assign.NewVar(mem, procs, errfmt) assign_b[builtin_i.declare] = new_var assign_b[builtin_i.typeset] = new_var assign_b[builtin_i.local] = new_var assign_b[builtin_i.export_] = builtin_assign.Export(mem, errfmt) assign_b[builtin_i.readonly] = builtin_assign.Readonly(mem, errfmt) # # Initialize Evaluators # arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt) expr_ev = None # type: expr_eval.OilEvaluator word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_b, arena, cmd_deps) #shell_ex = executor.ShellExecutor( # mem, exec_opts, mutable_opts, procs, builtins, search_path, # ext_prog, waiter, job_state, fd_state, errfmt) shell_ex = NullExecutor(exec_opts, mutable_opts, procs, builtins) # PromptEvaluator rendering is needed in non-interactive shells for @P. prompt_ev = prompt.Evaluator(lang, parse_ctx, mem) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) # Wire up circular dependencies. vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, cmd_ev, shell_ex, prompt_ev, tracer) # # Initialize builtins that depend on evaluators # # note: printf -v a[0] will give it same deps as 'unset' builtins[builtin_i.printf] = builtin_printf.Printf(mem, parse_ctx, errfmt) builtins[builtin_i.unset] = builtin_assign.Unset(mem, exec_opts, procs, parse_ctx, arith_ev, errfmt) builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev) #source_builtin = builtin_meta.Source(parse_ctx, search_path, cmd_ev, #fd_state, errfmt) #builtins[builtin_i.source] = source_builtin #builtins[builtin_i.dot] = source_builtin builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt) builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs, aliases, search_path) # These builtins take blocks, and thus need cmd_ev. builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt) #sig_state = process.SignalState() #sig_state.InitShell() #builtins[builtin_i.trap] = builtin_process.Trap(sig_state, cmd_deps.traps, # cmd_deps.trap_nodes, # parse_ctx, errfmt) if flag.c is not None: arena.PushSource(source.CFlag()) line_reader = reader.StringLineReader(flag.c, arena) # type: reader._Reader if flag.i: # -c and -i can be combined mutable_opts.set_interactive() elif flag.i: # force interactive raise NotImplementedError() else: if script_name is None: stdin = mylib.Stdin() arena.PushSource(source.Stdin('')) line_reader = reader.FileLineReader(stdin, arena) else: arena.PushSource(source.MainFile(script_name)) try: #f = fd_state.Open(script_name) f = mylib.open(script_name) except OSError as e: stderr_line("osh: Couldn't open %r: %s", script_name, pyutil.strerror_OS(e)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. c_parser = parse_ctx.MakeOshParser(line_reader) if exec_opts.interactive(): raise NotImplementedError() if exec_opts.noexec(): status = 0 try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) status = 2 if status == 0 : if flag.parser_mem_dump: # only valid in -n mode input_path = '/proc/%d/status' % posix.getpid() pyutil.CopyFile(input_path, flag.parser_mem_dump) ui.PrintAst(node, flag) else: if flag.parser_mem_dump: e_usage('--parser-mem-dump can only be used with -n') try: status = main_loop.Batch(cmd_ev, c_parser, arena, cmd_flags=cmd_eval.IsMainProgram) if cmd_ev.MaybeRunExitTrap(): status = cmd_ev.LastStatus() except util.UserExit as e: status = e.status # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub # don't because they call sys.exit(). if flag.runtime_mem_dump: input_path = '/proc/%d/status' % posix.getpid() pyutil.CopyFile(input_path, flag.runtime_mem_dump) # NOTE: We haven't closed the file opened with fd_state.Open return status