def _ReconcileTypes(rval, flag_a, flag_A, span_id): # type: (Optional[value_t], bool, bool, int) -> value_t """Check that -a and -A flags are consistent with RHS. Special case: () is allowed to mean empty indexed array or empty assoc array if the context is clear. Shared between NewVar and Readonly. """ if flag_a and rval is not None and rval.tag_() != value_e.MaybeStrArray: e_usage("Got -a but RHS isn't an array", span_id=span_id) if flag_A and rval: # Special case: declare -A A=() is OK. The () is changed to mean an empty # associative array. if rval.tag_() == value_e.MaybeStrArray: array_val = cast(value__MaybeStrArray, rval) if len(array_val.strs) == 0: return value.AssocArray({}) #return value.MaybeStrArray([]) if rval.tag_() != value_e.AssocArray: e_usage("Got -A but RHS isn't an associative array", span_id=span_id) return rval
def _Value(self, arg, span_id): # type: (str, int) -> value_t if self.valid is not None and arg not in self.valid: e_usage('got invalid argument %r to %r, expected one of: %s' % (arg, ('-' + self.name), '|'.join(self.valid)), span_id=span_id) return value.Str(arg)
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv[1:] attrs, arg_r = flag_spec.ParseLikeEcho('echo', cmd_val) arg = arg_types.echo(attrs.attrs) argv = arg_r.Rest() backslash_c = False # \c terminates input arg0_spid = cmd_val.arg_spids[0] if arg.e: new_argv = [] # type: List[str] for a in argv: parts = [] # type: List[str] lex = match.EchoLexer(a) while not backslash_c: id_, value = lex.Next() if id_ == Id.Eol_Tok: # Note: This is really a NUL terminator break tok = Token(id_, arg0_spid, value) p = word_compile.EvalCStringToken(tok) # Unusual behavior: '\c' prints what is there and aborts processing! if p is None: backslash_c = True break parts.append(p) new_argv.append(''.join(parts)) if backslash_c: # no more args either break # Replace it argv = new_argv if self.exec_opts.simple_echo(): n = len(argv) if n == 0: pass elif n == 1: self.f.write(argv[0]) else: # TODO: span_id could be more accurate e_usage( "takes at most one arg when simple_echo is on (hint: add quotes)" ) else: #log('echo argv %s', argv) for i, a in enumerate(argv): if i != 0: self.f.write(' ') # arg separator self.f.write(a) if not arg.n and not backslash_c: self.f.write('\n') return 0
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('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__Assign) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() attrs = flag_spec.Parse('export_', arg_r) arg = arg_types.export_(attrs.attrs) #arg = attrs if arg.f: e_usage( "doesn't accept -f because it's dangerous. " "(The code can usually be restructured with 'source')") if arg.p or len(cmd_val.pairs) == 0: return _PrintVariables(self.mem, cmd_val, attrs, True, builtin=_EXPORT) if arg.n: for pair in cmd_val.pairs: if pair.rval is not None: e_usage("doesn't accept RHS with -n", span_id=pair.spid) # NOTE: we don't care if it wasn't found, like bash. self.mem.ClearFlag(pair.var_name, state.ClearExport, scope_e.Dynamic) else: for pair in cmd_val.pairs: # NOTE: when rval is None, only flags are changed self.mem.SetVar(lvalue.Named(pair.var_name), pair.rval, scope_e.Dynamic, flags=state.SetExport) return 0
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, posix.strerror(e.errno), span_id=cmd_val.arg_spids[1]) return 1 self.dir_stack.Push(dest_dir) _PrintDirStack(self.dir_stack, SINGLE_LINE, self.mem.GetVar('HOME')) state.ExportGlobalString(self.mem, 'PWD', dest_dir) self.mem.SetPwd(dest_dir) return 0
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int argv = cmd_val.argv[1:] if len(argv) == 0: # umask() has a dumb API: you can't get it without modifying it first! # NOTE: dash disables interrupts around the two umask() calls, but that # shouldn't be a concern for us. Signal handlers won't call umask(). mask = posix.umask(0) posix.umask(mask) # print('0%03o' % mask) # octal format return 0 if len(argv) == 1: a = argv[0] try: new_mask = int(a, 8) except ValueError: # NOTE: This happens if we have '8' or '9' in the input too. stderr_line( "osh warning: umask with symbolic input isn't implemented") return 1 else: posix.umask(new_mask) return 0 e_usage('umask: unexpected arguments')
def ParseMore(spec, arg_r): # type: (flag_spec._FlagSpecAndMore, Reader) -> _Attributes """Return attributes and an index. Respects +, like set +eu We do NOT respect: WRONG: sh -cecho OK: sh -c echo WRONG: set -opipefail OK: set -o pipefail But we do accept these set -euo pipefail set -oeu pipefail set -oo pipefail errexit """ out = _Attributes(spec.defaults) quit = False while not arg_r.AtEnd(): arg = arg_r.Peek() if arg == '--': out.saw_double_dash = True arg_r.Next() break # NOTE: We don't yet support --rcfile=foo. Only --rcfile foo. if arg.startswith('--'): try: action = spec.actions_long[arg] except KeyError: e_usage('got invalid flag %r' % arg, span_id=arg_r.SpanId()) # TODO: Suffix could be 'bar' for --foo=bar action.OnMatch(None, None, arg_r, out) arg_r.Next() continue if arg.startswith('-') or arg.startswith('+'): char0 = arg[0] for ch in arg[1:]: #log('ch %r arg_r %s', ch, arg_r) try: action = spec.actions_short[ch] except KeyError: e_usage('got invalid flag %r' % ('-' + ch), span_id=arg_r.SpanId()) quit = action.OnMatch(char0, None, arg_r, out) arg_r.Next() # process the next flag if quit: break else: continue break # it's a regular arg return out
def ReadRequired(self, error_msg): # type: (str) -> str arg = self.Peek() if arg is None: # point at argv[0] e_usage(error_msg, span_id=self._FirstSpanId()) self.Next() return arg
def Parse(spec, arg_r): # type: (flag_spec._FlagSpec, Reader) -> _Attributes # NOTE about -: # 'set -' ignores it, vs set # 'unset -' or 'export -' seems to treat it as a variable name out = _Attributes(spec.defaults) while not arg_r.AtEnd(): arg = arg_r.Peek() if arg == '--': out.saw_double_dash = True arg_r.Next() break if arg.startswith('-') and len(arg) > 1: n = len(arg) for i in xrange(1, n): # parse flag combos like -rx ch = arg[i] if ch in spec.plus_flags: out.Set(ch, value.Str('-')) continue if ch in spec.arity0: # e.g. read -r out.SetTrue(ch) continue if ch in spec.arity1: # e.g. read -t1.0 action = spec.arity1[ch] # make sure we don't pass empty string for read -t attached_arg = arg[i + 1:] if i < n - 1 else None action.OnMatch(attached_arg, arg_r, out) break e_usage("doesn't accept flag %s" % ('-' + ch), span_id=arg_r.SpanId()) arg_r.Next() # next arg # Only accept + if there are ANY options defined, e.g. for declare +rx. elif len(spec.plus_flags) and arg.startswith('+') and len(arg) > 1: n = len(arg) for i in xrange(1, n): # parse flag combos like -rx ch = arg[i] if ch in spec.plus_flags: out.Set(ch, value.Str('+')) continue e_usage("doesn't accept option %s" % ('+' + ch), span_id=arg_r.SpanId()) arg_r.Next() # next arg else: # a regular arg break return out
def ReadRequired2(self, error_msg): # type: (str) -> Tuple[str, int] arg = self.Peek() if arg is None: # point at argv[0] e_usage(error_msg, span_id=self._FirstSpanId()) spid = self.spids[self.i] self.Next() return arg, spid
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 if len(cmd_val.arg_spids) > 1: e_usage('got extra argument', span_id=cmd_val.arg_spids[1]) if not _PopDirStack(self.mem, self.dir_stack, self.errfmt): return 1 # error _PrintDirStack(self.dir_stack, SINGLE_LINE, state.MaybeString(self.mem, ('HOME'))) return 0
def _SetToArg(action, suffix, arg_r, out): # type: (SetToArgAction, Optional[str], Reader, _Attributes) -> bool """ Perform the action. """ if suffix: # for the ',' in -d, arg = suffix else: arg_r.Next() arg = arg_r.Peek() if arg is None: e_usage('expected argument to %r' % ('-' + action.name), span_id=arg_r.SpanId()) # e.g. spec.LongFlag('--format', ['text', 'html']) # Should change to arg.Enum([...]) with tagswitch(action.flag_type) as case: if case(flag_type_e.Enum): alts = cast(flag_type__Enum, action.flag_type).alts if arg not in alts: e_usage('got invalid argument %r to %r, expected one of: %s' % (arg, ('-' + action.name), ', '.join(alts)), span_id=arg_r.SpanId()) val = value.Str(arg) # type: value_t elif case(flag_type_e.Str): val = value.Str(arg) elif case(flag_type_e.Int): try: i = int(arg) except ValueError: e_usage('expected integer after %s, got %r' % ('-' + action.name, arg), span_id=arg_r.SpanId()) # So far all our int values are > 0, so use -1 as the 'unset' value if i < 0: e_usage('got invalid integer for %s: %s' % ('-' + action.name, arg), span_id=arg_r.SpanId()) val = value.Int(i) elif case(flag_type_e.Float): try: val = value.Float(float(arg)) except ValueError: e_usage('expected number after %r, got %r' % ('-' + action.name, arg), span_id=arg_r.SpanId()) else: raise AssertionError() out.Set(action.name, val) return action.quit_parsing_flags
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int attrs, arg_r = flag_spec.ParseOilCmdVal('forkwait', cmd_val) arg, span_id = arg_r.Peek2() if arg is not None: e_usage('got unexpected argument %r' % arg, span_id=span_id) if cmd_val.block is None: e_usage('expected a block') return self.shell_ex.RunSubshell(cmd_val.block)
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 _Value(self, arg, span_id): # type: (str, int) -> value_t try: f = float(arg) except ValueError: e_usage('expected number after %r, got %r' % ('-' + self.name, arg), span_id=span_id) # So far all our float values are > 0, so use -1.0 as the 'unset' value # corner case: this treats -0.0 as 0.0! if f < 0: e_usage('got invalid float for %s: %s' % ('-' + self.name, arg), span_id=span_id) return value.Float(f)
def OnMatch(self, attached_arg, arg_r, out): # type: (Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" arg_r.Next() # always advance arg = arg_r.Peek() if arg is None: e_usage('Expected argument for action') attr_name = arg # Validate the option name against a list of valid names. if attr_name not in self.names: e_usage('Invalid action name %r' % arg) out.actions.append(attr_name) return False
def _Value(self, arg, span_id): # type: (str, int) -> value_t try: i = int(arg) except ValueError: e_usage('expected integer after %s, got %r' % ('-' + self.name, arg), span_id=span_id) # So far all our int values are > 0, so use -1 as the 'unset' value # corner case: this treats -0 as 0! if i < 0: e_usage('got invalid integer for %s: %s' % ('-' + self.name, arg), span_id=span_id) return value.Int(i)
def OnMatch(self, attached_arg, arg_r, out): # type: (Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" if attached_arg is not None: # for the ',' in -d, arg = attached_arg else: arg_r.Next() arg = arg_r.Peek() if arg is None: e_usage('expected argument to %r' % ('-' + self.name), span_id=arg_r.SpanId()) val = self._Value(arg, arg_r.SpanId()) out.Set(self.name, val) return self.quit_parsing_flags
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int num_args = len(cmd_val.argv) - 1 if num_args == 0: n = 1 elif num_args == 1: arg = cmd_val.argv[1] try: n = int(arg) except ValueError: e_usage("Invalid shift argument %r" % arg) else: e_usage('got too many arguments') return self.mem.Shift(n)
def _UnsetVar(self, arg, spid, proc_fallback): # type: (str, int, bool) -> bool """ Returns: bool: whether the 'unset' builtin should succeed with code 0. """ arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg) arena.PushSource(source.ArgvWord(spid)) try: anode = a_parser.Parse() except error.Parse as e: # show parse error ui.PrettyPrintError(e, arena) # point to word e_usage('Invalid unset expression', span_id=spid) finally: arena.PopSource() lval = self.arith_ev.EvalArithLhs(anode, spid) # Prevent attacks like these by default: # # unset -v 'A["$(echo K; rm *)"]' if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_die( 'Expected a variable name. Expressions are allowed with shopt -s eval_unsafe_arith', span_id=spid) #log('lval %s', lval) found = False try: # not strict found = self.mem.Unset(lval, scope_e.Dynamic, False) except error.Runtime as e: # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't # fail because it's a builtin. So I guess the same is true of 'unset'. e.span_id = spid ui.PrettyPrintError(e, arena) return False if proc_fallback and not found: if arg in self.funcs: del self.funcs[arg] return True
def OnMatch(self, prefix, suffix, arg_r, out): # type: (Optional[str], Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" if suffix: # '0' in --verbose=0 if suffix in ('0', 'F', 'false', 'False'): b = False elif suffix in ('1', 'T', 'true', 'Talse'): b = True else: e_usage('got invalid argument to boolean flag: %r' % suffix) else: b = True out.Set(self.name, value.Bool(b)) return False
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 _UnsetVar(self, arg, spid, proc_fallback): # type: (str, int, bool) -> bool """ Returns: bool: whether the 'unset' builtin should succeed with code 0. """ arena = self.parse_ctx.arena a_parser = self.parse_ctx.MakeArithParser(arg) with alloc.ctx_Location(arena, source.ArgvWord(spid)): try: anode = a_parser.Parse() except error.Parse as e: ui.PrettyPrintError(e, arena) # show parse error e_usage('Invalid unset expression', span_id=spid) lval = self.arith_ev.EvalArithLhs(anode, spid) # Prevent attacks like these by default: # # unset -v 'A["$(echo K; rm *)"]' if not self.exec_opts.eval_unsafe_arith( ) and lval.tag_() != lvalue_e.Named: e_usage( 'expected a variable name. shopt -s eval_unsafe_arith allows expressions', span_id=spid) #log('lval %s', lval) found = False try: # Note: This has 'setvar' semantics. It could be 'setref' too? # So it composes? found = self.mem.Unset(lval, False) except error.Runtime as e: # note: in bash, myreadonly=X fails, but declare myreadonly=X doens't # fail because it's a builtin. So I guess the same is true of 'unset'. e.span_id = spid ui.PrettyPrintError(e, arena) return False if proc_fallback and not found: if arg in self.funcs: del self.funcs[arg] return True
def OnMatch(self, attached_arg, arg_r, out): # type: (Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" if attached_arg is not None: # '0' in --verbose=0 if attached_arg in ('0', 'F', 'false', 'False'): # TODO: incorrect translation b = False elif attached_arg in ('1', 'T', 'true', 'Talse'): b = True else: e_usage('got invalid argument to boolean flag: %r' % attached_arg) else: b = True out.Set(self.name, value.Bool(b)) return False
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 OnMatch(self, attached_arg, arg_r, out): # type: (Optional[str], Reader, _Attributes) -> bool """Called when the flag matches.""" b = (attached_arg == '-') #log('SetNamedOption %r %r %r', prefix, suffix, arg_r) arg_r.Next() # always advance arg = arg_r.Peek() if arg is None: # triggers on 'set -O' in addition to 'set -o' (meh OK) out.show_options = True return True # quit parsing attr_name = arg # Validate the option name against a list of valid names. if attr_name not in self.names: e_usage('got invalid option %r' % arg, span_id=arg_r.SpanId()) changes = out.shopt_changes if self.shopt else out.opt_changes changes.append((attr_name, b)) return False
def ParseOil(spec, arg_r): # type: (flag_spec._OilFlags, Reader) -> Tuple[_Attributes, int] out = _Attributes(spec.defaults) while not arg_r.AtEnd(): arg = arg_r.Peek() if arg == '--': out.saw_double_dash = True arg_r.Next() break if arg == '-': # a valid argument break # TODO: Use FLAG_RE above if arg.startswith('-'): m = libc.regex_match(_FLAG_ERE, arg) if m is None: e_usage('Invalid flag syntax: %r' % arg) _, flag, val = m # group 0 is ignored; the whole match # TODO: we don't need arity 1 or 0? Booleans are like --verbose=1, # --verbose (equivalent to turning it on) or --verbose=0. name = flag.replace('-', '_') if name in spec.arity1: # e.g. read -t1.0 action = spec.arity1[name] if val.startswith('='): suffix = val[1:] # could be empty, but remove = if any else: suffix = None action.OnMatch(suffix, arg_r, out) else: e_usage('Unrecognized flag %r' % arg) arg_r.Next() # next arg else: # a regular arg break return out, arg_r.i