def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('help', cmd_val) #arg = arg_types.help(attrs.attrs) topic, blame_spid = arg_r.Peek2() if topic is None: topic = 'help' blame_spid = runtime.NO_SPID else: arg_r.Next() try: contents = self.loader.Get('_devbuild/help/%s' % topic) except IOError: # Notes: # 1. bash suggests: # man -k zzz # info zzz # help help # We should do something smarter. # 2. This also happens on 'build/dev.sh minimal', which isn't quite # accurate. We don't have an exact list of help topics! # 3. This is mostly an interactive command. Is it obnoxious to # quote the line of code? self.errfmt.Print_('no help topics match %r' % topic, span_id=blame_spid) return 1 print(contents) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('hash', cmd_val) arg = arg_types.hash(attrs.attrs) rest = arg_r.Rest() if arg.r: if len(rest): e_usage('got extra arguments after -r') self.search_path.ClearCache() return 0 status = 0 if len(rest): for cmd in rest: # enter in cache full_path = self.search_path.CachedLookup(cmd) if full_path is None: stderr_line('hash: %r not found', cmd) status = 1 else: # print cache commands = self.search_path.CachedCommands() commands.sort() for cmd in commands: print(cmd) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val) arg = arg_types.read(attrs.attrs) names = arg_r.Rest() fd = self.stdin.fileno() if arg.t >= 0.0: if arg.t != 0.0: e_die("read -t isn't implemented (except t=0)") else: return 0 if pyos.InputAvailable(fd) else 1 bits = 0 if self.stdin.isatty(): bits |= pyos.TERM_ICANON if arg.s: # silent bits |= pyos.TERM_ECHO if arg.p is not None: # only if tty mylib.Stderr().write(arg.p) if bits == 0: status = self._Read(arg, names) else: term = pyos.TermState(fd, ~bits) try: status = self._Read(arg, names) finally: term.Restore() return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int _, arg_r = flag_spec.ParseCmdVal('alias', cmd_val) argv = arg_r.Rest() if len(argv) == 0: for name in sorted(self.aliases): alias_exp = self.aliases[name] # This is somewhat like bash, except we use %r for ''. print('alias %s=%r' % (name, alias_exp)) return 0 status = 0 for i, arg in enumerate(argv): name, alias_exp = mylib.split_once(arg, '=') if alias_exp is None: # if we get a plain word without, print alias alias_exp = self.aliases.get(name) if alias_exp is None: self.errfmt.Print_('No alias named %r' % name, span_id=cmd_val.arg_spids[i]) status = 1 else: print('alias %s=%r' % (name, alias_exp)) else: self.aliases[name] = alias_exp #print(argv) #log('AFTER ALIAS %s', aliases) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('command', cmd_val) arg = arg_types.command(attrs.attrs) if arg.v: status = 0 names = arg_r.Rest() for kind, argument in _ResolveNames(names, self.funcs, self.aliases, self.search_path): if kind is None: status = 1 # nothing printed, but we fail else: # This is for -v, -V is more detailed. print(argument) return status # shift by one cmd_val = cmd_value.Argv(cmd_val.argv[1:], cmd_val.arg_spids[1:], None) # If we respected do_fork here instead of passing True, the case # 'command date | wc -l' would take 2 processes instead of 3. But no other # shell does that, and this rare case isn't worth the bookkeeping. # See test/syscall return self.shell_ex.RunSimpleCommand(cmd_val, True, call_procs=False)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # There are no flags, but we need it to respect -- _, arg_r = flag_spec.ParseCmdVal('eval', cmd_val) if self.exec_opts.simple_eval_builtin(): code_str, eval_spid = arg_r.ReadRequired2('requires code string') if not arg_r.AtEnd(): e_usage('requires exactly 1 argument') else: code_str = ' '.join(arg_r.Rest()) # code_str could be EMPTY, so just use the first one eval_spid = cmd_val.arg_spids[0] line_reader = reader.StringLineReader(code_str, self.arena) c_parser = self.parse_ctx.MakeOshParser(line_reader) src = source.EvalArg(eval_spid) with dev.ctx_Tracer(self.tracer, 'eval', None): with alloc.ctx_Location(self.arena, src): return main_loop.Batch(self.cmd_ev, c_parser, self.arena, cmd_flags=cmd_eval.IsEvalSource)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('shopt', cmd_val) arg = arg_types.shopt(attrs.attrs) opt_names = arg_r.Rest() if arg.p: # print values if arg.o: # use set -o names self.mutable_opts.ShowOptions(opt_names) else: self.mutable_opts.ShowShoptOptions(opt_names) return 0 if arg.q: # query values for name in opt_names: index = match.MatchOption(name) if index == 0: return 2 # bash gives 1 for invalid option; 2 is better if not self.mutable_opts.opt0_array[index]: return 1 # at least one option is not true return 0 # all options are true if arg.s: b = True elif arg.u: b = False else: # If no flags are passed, print the options. bash prints uses a # different format for 'shopt', but we use the same format as 'shopt # -p'. self.mutable_opts.ShowShoptOptions(opt_names) return 0 if cmd_val.block: opt_nums = [] # type: List[int] for name in opt_names: index = match.MatchOption(name) if index == 0: # TODO: compute span_id e_usage('got invalid option %r' % name) opt_nums.append(index) with state.ctx_Option(self.mutable_opts, opt_nums, b): unused = self.cmd_ev.EvalBlock(cmd_val.block) return 0 # cd also returns 0 # Otherwise, set options. for name in opt_names: #if arg.o: # self.mutable_opts.SetOption(name, b) #else: # We allow set -o options here self.mutable_opts.SetShoptOption(name, b) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int # NOTE: This builtin doesn't do anything in non-interactive mode in bash? # It silently exits zero. # zsh -c 'history' produces an error. readline_mod = self.readline_mod if not readline_mod: e_usage("is disabled because Oil wasn't compiled with 'readline'") attrs, arg_r = flag_spec.ParseCmdVal('history', cmd_val) arg = arg_types.history(attrs.attrs) # Clear all history if arg.c: readline_mod.clear_history() return 0 # Delete history entry by id number if arg.d >= 0: cmd_index = arg.d - 1 try: readline_mod.remove_history_item(cmd_index) except ValueError: e_usage("couldn't find item %d" % arg.d) return 0 # Returns 0 items in non-interactive mode? num_items = readline_mod.get_current_history_length() #log('len = %d', num_items) rest = arg_r.Rest() if len(rest) == 0: start_index = 1 elif len(rest) == 1: arg0 = rest[0] try: num_to_show = int(arg0) except ValueError: e_usage('got invalid argument %r' % arg0) start_index = max(1, num_items + 1 - num_to_show) else: e_usage('got many arguments') # TODO: # - Exclude lines that don't parse from the history! bash and zsh don't do # that. # - Consolidate multiline commands. for i in xrange(start_index, num_items+1): # 1-based index item = readline_mod.get_history_item(i) self.f.write('%5d %s\n' % (i, item)) return 0
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(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 Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('pwd', cmd_val) arg = arg_types.pwd(attrs.attrs) # NOTE: 'pwd' will succeed even if the directory has disappeared. Other # shells behave that way too. pwd = self.mem.pwd # '-L' is the default behavior; no need to check it # TODO: ensure that if multiple flags are provided, the *last* one overrides # the others if arg.P: pwd = libc.realpath(pwd) print(pwd) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int _, arg_r = flag_spec.ParseCmdVal('unalias', cmd_val) argv = arg_r.Rest() if len(argv) == 0: e_usage('requires an argument') status = 0 for i, name in enumerate(argv): if name in self.aliases: del self.aliases[name] else: self.errfmt.Print_('No alias named %r' % name, span_id=cmd_val.arg_spids[i]) status = 1 return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('type', cmd_val) arg = arg_types.type(attrs.attrs) if arg.f: funcs = {} # type: Dict[str, command__ShFunction] else: funcs = self.funcs status = 0 r = _ResolveNames(arg_r.Rest(), funcs, self.aliases, self.search_path) for kind, name in r: if kind is None: self.errfmt.StderrLine('type: %r not found' % name) status = 1 # nothing printed, but we fail else: if arg.t: print(kind) elif arg.p: if kind == 'file': print(name) elif arg.P: if kind == 'file': print(name) else: resolved = self.search_path.Lookup(name) if resolved is None: status = 1 else: print(resolved) else: # Alpine's abuild relies on this text because busybox ash doesn't have # -t! # ash prints "is a shell function" instead of "is a function", but the # regex accouts for that. print('%s is a %s' % (name, kind)) if kind == 'function': # bash prints the function body, busybox ash doesn't. pass return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('mapfile', cmd_val) # TODO: Implement flags to mapfile #arg = arg_types.mapfile(attrs.attrs) var_name, _ = arg_r.Peek2() if var_name is None: var_name = 'MAPFILE' lines = [] # type: List[str] while True: line = self.f.readline() if len(line) == 0: break lines.append(line) state.SetArrayDynamic(self.mem, var_name, lines) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('mapfile', cmd_val) arg = arg_types.mapfile(attrs.attrs) var_name, _ = arg_r.Peek2() if var_name is None: var_name = 'MAPFILE' lines = [] # type: List[str] while True: line = self.f.readline() if len(line) == 0: break if arg.t and line.endswith('\n'): line = line[:-1] lines.append(line) state.SetArrayDynamic(self.mem, var_name, lines) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('shopt', cmd_val) arg = arg_types.shopt(attrs.attrs) opt_names = arg_r.Rest() if arg.p: # print values if arg.o: # use set -o names self.exec_opts.ShowOptions(opt_names) else: self.exec_opts.ShowShoptOptions(opt_names) return 0 if arg.q: # query values for name in opt_names: index = match.MatchOption(name) if index == 0: return 2 # bash gives 1 for invalid option; 2 is better if not self.exec_opts.opt_array[index]: return 1 # at least one option is not true return 0 # all options are true if arg.s: b = True elif arg.u: b = False else: # bash prints uses a different format for 'shopt', but we use the # same format as 'shopt -p'. self.exec_opts.ShowShoptOptions(opt_names) return 0 # Otherwise, set options. for name in opt_names: if arg.o: self.exec_opts.SetOption(name, b) else: self.exec_opts.SetShoptOption(name, b) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('dirs', cmd_val) arg = arg_types.dirs(attrs.attrs) home_dir = state.MaybeString(self.mem, 'HOME') style = SINGLE_LINE # Following bash order of flag priority if arg.l: home_dir = None # disable pretty ~ if arg.c: self.dir_stack.Reset() return 0 elif arg.v: style = WITH_LINE_NUMBERS elif arg.p: style = WITHOUT_LINE_NUMBERS _PrintDirStack(self.dir_stack, style, home_dir) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('unset', cmd_val) arg = arg_types.unset(attrs.attrs) argv, arg_spids = arg_r.Rest2() for i, name in enumerate(argv): spid = arg_spids[i] if arg.f: if name in self.funcs: del self.funcs[name] elif arg.v: if not self._UnsetVar(name, spid, False): return 1 else: # proc_fallback: Try to delete var first, then func. if not self._UnsetVar(name, spid, True): return 1 return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val) arg = arg_types.read(attrs.attrs) names = arg_r.Rest() # Don't respect any of the other options here? This is buffered I/O. if arg.line: # read --line var_name, var_spid = arg_r.Peek2() if var_name is None: var_name = '_line' else: if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] arg_r.Next() next_arg, next_spid = arg_r.Peek2() if next_arg is not None: raise error.Usage('got extra argument', span_id=next_spid) return self._Line(arg, var_name) if arg.q: e_usage('--qsn can only be used with --line') if arg.all: # read --all var_name, var_spid = arg_r.Peek2() if var_name is None: var_name = '_all' else: if var_name.startswith(':'): # optional : sigil var_name = var_name[1:] arg_r.Next() next_arg, next_spid = arg_r.Peek2() if next_arg is not None: raise error.Usage('got extra argument', span_id=next_spid) return self._All(var_name) if arg.q: e_usage('--qsn not implemented yet') fd = self.stdin.fileno() if arg.t >= 0.0: if arg.t != 0.0: e_die("read -t isn't implemented (except t=0)") else: return 0 if pyos.InputAvailable(fd) else 1 bits = 0 if self.stdin.isatty(): bits |= pyos.TERM_ICANON if arg.s: # silent bits |= pyos.TERM_ECHO if arg.p is not None: # only if tty mylib.Stderr().write(arg.p) if bits == 0: status = self._Read(arg, names) else: term = pyos.TermState(fd, ~bits) try: status = self._Read(arg, names) finally: term.Restore() return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int """ printf: printf [-v var] format [argument ...] """ attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val) arg = arg_types.printf(attrs.attrs) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) parser = _FormatStringParser(lexer) with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)): try: parts = parser.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] # type: List[str] arg_index = 0 num_args = len(varargs) backslash_c = False while True: for part in parts: UP_part = part if part.tag_() == printf_part_e.Literal: part = cast(printf_part__Literal, UP_part) token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token) out.append(s) elif part.tag_() == printf_part_e.Percent: part = cast(printf_part__Percent, UP_part) flags = [] # type: List[str] if len(part.flags) > 0: for flag_token in part.flags: flags.append(flag_token.val) width = -1 # nonexistent if part.width: if part.width.id in (Id.Format_Num, Id.Format_Zero): width_str = part.width.val width_spid = part.width.span_id elif part.width.id == Id.Format_Star: if arg_index < num_args: width_str = varargs[arg_index] width_spid = spids[arg_index] arg_index += 1 else: width_str = '' # invalid width_spid = runtime.NO_SPID else: raise AssertionError() try: width = int(width_str) except ValueError: if width_spid == runtime.NO_SPID: width_spid = part.width.span_id self.errfmt.Print_("printf got invalid width %r" % width_str, span_id=width_spid) return 1 precision = -1 # nonexistent if part.precision: if part.precision.id == Id.Format_Dot: precision_str = '0' precision_spid = part.precision.span_id elif part.precision.id in (Id.Format_Num, Id.Format_Zero): precision_str = part.precision.val precision_spid = part.precision.span_id elif part.precision.id == Id.Format_Star: if arg_index < num_args: precision_str = varargs[arg_index] precision_spid = spids[arg_index] arg_index += 1 else: precision_str = '' precision_spid = runtime.NO_SPID else: raise AssertionError() try: precision = int(precision_str) except ValueError: if precision_spid == runtime.NO_SPID: precision_spid = part.precision.span_id self.errfmt.Print_( 'printf got invalid precision %r' % precision_str, span_id=precision_spid) return 1 #log('index=%d n=%d', arg_index, num_args) if arg_index < num_args: s = varargs[arg_index] word_spid = spids[arg_index] arg_index += 1 else: s = '' word_spid = runtime.NO_SPID typ = part.type.val if typ == 's': if precision >= 0: s = s[:precision] # truncate elif typ == 'q': s = qsn.maybe_shell_encode(s) elif typ == 'b': # Process just like echo -e, except \c handling is simpler. c_parts = [] # type: List[str] lex = match.EchoLexer(s) while True: id_, tok_val = lex.Next() if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator break # TODO: add span_id from argv tok = Token(id_, runtime.NO_SPID, tok_val) p = word_compile.EvalCStringToken(tok) # Unusual behavior: '\c' aborts processing! if p is None: backslash_c = True break c_parts.append(p) s = ''.join(c_parts) elif typ in 'diouxX' or part.type.id == Id.Format_Time: try: d = int(s) except ValueError: if len(s) >= 1 and s[0] in '\'"': # TODO: utf-8 decode s[1:] to be more correct. Probably # depends on issue #366, a utf-8 library. # Note: len(s) == 1 means there is a NUL (0) after the quote.. d = ord(s[1]) if len(s) >= 2 else 0 elif part.type.id == Id.Format_Time and len( s) == 0 and word_spid == runtime.NO_SPID: # Note: No argument means -1 for %(...)T as in Bash Reference # Manual 4.2 "If no argument is specified, conversion behaves # as if -1 had been given." d = -1 else: if word_spid == runtime.NO_SPID: # Blame the format string blame_spid = part.type.span_id else: blame_spid = word_spid self.errfmt.Print_( 'printf expected an integer, got %r' % s, span_id=blame_spid) return 1 if typ in 'di': s = str(d) elif typ in 'ouxX': if d < 0: e_die( "Can't format negative number %d with %%%s", d, typ, span_id=part.type.span_id) if typ == 'u': s = str(d) elif typ == 'o': s = mylib.octal(d) elif typ == 'x': s = mylib.hex_lower(d) elif typ == 'X': s = mylib.hex_upper(d) elif part.type.id == Id.Format_Time: # %(...)T # Initialize timezone: # `localtime' uses the current timezone information initialized # by `tzset'. The function `tzset' refers to the environment # variable `TZ'. When the exported variable `TZ' is present, # its value should be reflected in the real environment # variable `TZ' before call of `tzset'. # # Note: unlike LANG, TZ doesn't seem to change behavior if it's # not exported. # # TODO: In Oil, provide an API that doesn't rely on libc's # global state. tzcell = self.mem.GetCell('TZ') if tzcell and tzcell.exported and tzcell.val.tag_( ) == value_e.Str: tzval = cast(value__Str, tzcell.val) posix.putenv('TZ', tzval.s) time_.tzset() # Handle special values: # User can specify two special values -1 and -2 as in Bash # Reference Manual 4.2: "Two special argument values may be # used: -1 represents the current time, and -2 represents the # time the shell was invoked." from # https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf if d == -1: # the current time ts = time_.time() elif d == -2: # the shell start time ts = self.shell_start_time else: ts = d s = time_.strftime(typ[1:-2], time_.localtime(ts)) if precision >= 0: s = s[:precision] # truncate else: raise AssertionError() else: raise AssertionError() if width >= 0: if len(flags): if '-' in flags: s = s.ljust(width, ' ') elif '0' in flags: s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) else: raise AssertionError() if backslash_c: # 'printf %b a\cb xx' - \c terminates processing! break if arg_index >= num_args: break # Otherwise there are more args. So cycle through the loop once more to # implement the 'arg recycling' behavior. result = ''.join(out) if arg.v is not None: # TODO: get the span_id for arg.v! v_spid = runtime.NO_SPID arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg.v) with alloc.ctx_Location(arena, source.ArgvWord(v_spid)): try: anode = a_parser.Parse() except error.Parse as e: ui.PrettyPrintError(e, arena) # show parse error e_usage('Invalid -v expression', span_id=v_spid) lval = self.arith_ev.EvalArithLhs(anode, v_spid) if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_usage( '-v expected a variable name. shopt -s eval_unsafe_arith allows expressions', span_id=v_spid) state.SetRef(self.mem, lval, value.Str(result)) else: mylib.Stdout().write(result) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('trap', cmd_val) arg = arg_types.trap(attrs.attrs) if arg.p: # Print registered handlers for name, value in self.traps.iteritems(): # The unit tests rely on this being one line. # bash prints a line that can be re-parsed. print('%s %s' % (name, value.__class__.__name__)) return 0 if arg.l: # List valid signals and hooks ordered = _SIGNAL_NAMES.items() ordered.sort(key=lambda x: x[1]) for name in _HOOK_NAMES: print(' %s' % name) for name, int_val in ordered: print('%2d %s' % (int_val, name)) return 0 code_str = arg_r.ReadRequired('requires a code string') sig_spec, sig_spid = arg_r.ReadRequired2('requires a signal or hook name') # sig_key is NORMALIZED sig_spec: a signal number string or string hook # name. sig_key = None # type: Optional[str] sig_num = None if sig_spec in _HOOK_NAMES: sig_key = sig_spec elif sig_spec == '0': # Special case sig_key = 'EXIT' else: sig_num = _GetSignalNumber(sig_spec) if sig_num is not None: sig_key = str(sig_num) if sig_key is None: self.errfmt.Print("Invalid signal or hook %r", sig_spec, span_id=cmd_val.arg_spids[2]) return 1 # NOTE: sig_spec isn't validated when removing handlers. if code_str == '-': if sig_key in _HOOK_NAMES: try: del self.traps[sig_key] except KeyError: pass return 0 if sig_num is not None: try: del self.traps[sig_key] except KeyError: pass self.sig_state.RemoveUserTrap(sig_num) return 0 raise AssertionError('Signal or trap') # Try parsing the code first. node = self._ParseTrapCode(code_str) if node is None: return 1 # ParseTrapCode() prints an error for us. # Register a hook. if sig_key in _HOOK_NAMES: if sig_key in ('ERR', 'RETURN', 'DEBUG'): stderr_line("osh warning: The %r hook isn't implemented", sig_spec) self.traps[sig_key] = _TrapHandler(node, self.nodes_to_run) return 0 # Register a signal. if sig_num is not None: handler = _TrapHandler(node, self.nodes_to_run) # For signal handlers, the traps dictionary is used only for debugging. self.traps[sig_key] = handler if sig_num in (signal.SIGKILL, signal.SIGSTOP): self.errfmt.Print("Signal %r can't be handled", sig_spec, span_id=sig_spid) # Other shells return 0, but this seems like an obvious error return 1 self.sig_state.AddUserTrap(sig_num, handler) return 0 raise AssertionError('Signal or trap')
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('wait', cmd_val) arg = arg_types.wait(attrs.attrs) job_ids, arg_spids = arg_r.Rest2() if arg.n: # wait -n returns the exit status of the JOB. # You don't know WHICH process, which is odd. # TODO: this should wait for the next JOB, which may be multiple # processes. # Bash has a wait_for_any_job() function, which loops until the jobs # table changes. # # target_count = self.job_state.NumRunning() - 1 # while True: # if not self.waiter.WaitForOne(): # break # # if self.job_state.NumRunning == target_count: # break # #log('wait next') if self.waiter.WaitForOne(): return self.waiter.last_status else: return 127 # nothing to wait for if len(job_ids) == 0: #log('wait all') i = 0 while True: # BUG: If there is a STOPPED process, this will hang forever, because # we don't get ECHILD. # Not sure it matters since you can now Ctrl-C it. if not self.waiter.WaitForOne(): break # nothing to wait for i += 1 if self.job_state.NoneAreRunning(): break log('Waited for %d processes', i) return 0 # Get list of jobs. Then we need to check if they are ALL stopped. # Returns the exit code of the last one on the COMMAND LINE, not the exit # code of last one to FINISH. status = 1 # error for i, job_id in enumerate(job_ids): span_id = arg_spids[i] # The % syntax is sort of like ! history sub syntax, with various queries. # https://stackoverflow.com/questions/35026395/bash-what-is-a-jobspec if job_id.startswith('%'): raise error.Usage( "doesn't support bash-style jobspecs (got %r)" % job_id, span_id=span_id) # Does it look like a PID? try: pid = int(job_id) except ValueError: raise error.Usage('expected PID or jobspec, got %r' % job_id, span_id=span_id) job = self.job_state.JobFromPid(pid) if job is None: self.errfmt.Print("%s isn't a child of this shell", pid, span_id=span_id) return 127 # TODO: Does this wait for pipelines? job_status = job.JobWait(self.waiter) UP_job_status = job_status with tagswitch(job_status) as case: if case(job_status_e.Proc): job_status = cast(job_status__Proc, UP_job_status) status = job_status.code elif case(job_status_e.Pipeline): # TODO: handle PIPESTATUS? job_status = cast(job_status__Pipeline, UP_job_status) # Is this right? status = job_status.codes[-1] else: raise AssertionError return status
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 Run(self, cmd_val): # type: (cmd_value__Argv) -> int """ printf: printf [-v var] format [argument ...] """ attrs, arg_r = flag_spec.ParseCmdVal('printf', cmd_val) arg = arg_types.printf(attrs.attrs) fmt, fmt_spid = arg_r.ReadRequired2('requires a format string') varargs, spids = arg_r.Rest2() #log('fmt %s', fmt) #log('vals %s', vals) arena = self.parse_ctx.arena if fmt in self.parse_cache: parts = self.parse_cache[fmt] else: line_reader = reader.StringLineReader(fmt, arena) # TODO: Make public lexer = self.parse_ctx._MakeLexer(line_reader) parser = _FormatStringParser(lexer) with alloc.ctx_Location(arena, source.ArgvWord(fmt_spid)): try: parts = parser.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] # type: List[str] status = self._Format(parts, varargs, spids, out) if status != 0: return status # failure result = ''.join(out) if arg.v is not None: # TODO: get the span_id for arg.v! v_spid = runtime.NO_SPID arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg.v) with alloc.ctx_Location(arena, source.ArgvWord(v_spid)): try: anode = a_parser.Parse() except error.Parse as e: ui.PrettyPrintError(e, arena) # show parse error e_usage('Invalid -v expression', span_id=v_spid) lval = self.arith_ev.EvalArithLhs(anode, v_spid) if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_usage( '-v expected a variable name. shopt -s eval_unsafe_arith allows expressions', span_id=v_spid) state.SetRef(self.mem, lval, value.Str(result)) else: mylib.Stdout().write(result) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseCmdVal('read', cmd_val) arg = arg_types.read(attrs.attrs) names = arg_r.Rest() if arg.n >= 0: # read a certain number of bytes (-1 means unset) if len(names): name = names[0] else: name = 'REPLY' # default variable name status = 0 stdin_fd = self.stdin.fileno() if self.stdin.isatty(): # set stdin to read in unbuffered mode s = passwd.ReadBytesFromTerminal(stdin_fd, arg.n) else: chunks = [] # type: List[str] n = arg.n while n > 0: chunk = posix.read(stdin_fd, n) # read at up to N chars if len(chunk) == 0: break chunks.append(chunk) n -= len(chunk) s = ''.join(chunks) # DIdn't read all the bytes we wanted if len(s) != n: status = 1 state.SetStringDynamic(self.mem, name, s) # NOTE: Even if we don't get n bytes back, there is no error? return status if len(names) == 0: names.append('REPLY') # leftover words assigned to the last name if arg.a is not None: max_results = 0 # no max else: max_results = len(names) if arg.d is not None: if len(arg.d): delim_char = arg.d[0] else: delim_char = '\0' # -d '' delimits by NUL else: delim_char = '\n' # read a line # We have to read more than one line if there is a line continuation (and # it's not -r). parts = [] # type: List[mylib.BufWriter] join_next = False status = 0 while True: line, eof = ReadLineFromStdin(delim_char) if eof: # status 1 to terminate loop. (This is true even though we set # variables). status = 1 #log('LINE %r', line) if len(line) == 0: break spans = self.splitter.SplitForRead(line, not arg.r) done, join_next = _AppendParts(line, spans, max_results, join_next, parts) #log('PARTS %s continued %s', parts, continued) if done: break entries = [buf.getvalue() for buf in parts] num_parts = len(entries) if arg.a is not None: state.SetArrayDynamic(self.mem, arg.a, entries) else: for i in xrange(max_results): if i < num_parts: s = entries[i] else: s = '' # if there are too many variables #log('read: %s = %s', names[i], s) state.SetStringDynamic(self.mem, names[i], s) return status