def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') spec = self.spec_cache.get(spec_str) if spec is None: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, flag_char = _GetOpts(spec, user_argv, self.my_state, self.errfmt) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, flag_char) else: # NOTE: The builtin has PARTIALLY set state. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = self.mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) user_argv = arg_r.Rest() or self.mem.GetArgv() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def GetOpts(argv, mem): """ Vars to set: OPTIND - initialized to 1 at startup OPTARG - argument Vars used: OPTERR: disable printing of error messages """ # TODO: need to handle explicit args. try: # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = argv[0] var_name = argv[1] except IndexError: raise args.UsageError('getopts optstring name [arg]') try: spec = _GETOPTS_CACHE[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) _GETOPTS_CACHE[spec_str] = spec # These errors are fatal errors, not like the builtin exiting with code 1. # Because the invariants of the shell have been violated! v = mem.GetVar('OPTIND') if v.tag != value_e.Str: e_die('OPTIND should be a string, got %r', v) try: optind = int(v.s) except ValueError: e_die("OPTIND doesn't look like an integer, got %r", v.s) user_argv = argv[2:] or mem.GetArgv() status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(mem, var_name, opt_char) state.SetStringDynamic(mem, 'OPTARG', optarg) state.SetStringDynamic(mem, 'OPTIND', str(optind)) return status
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # NOTE: If first char is a colon, error reporting is different. Alpine # might not use that? spec_str = arg_r.ReadRequired('requires an argspec') var_name, var_spid = arg_r.ReadRequired2( 'requires the name of a variable to set') try: spec = self.spec_cache[spec_str] except KeyError: spec = _ParseOptSpec(spec_str) self.spec_cache[spec_str] = spec # TODO: OPTIND could be value.Int? try: optind = state.GetInteger(self.mem, 'OPTIND') except error.Runtime as e: self.errfmt.Print_(e.UserErrorString()) return 1 user_argv = self.mem.GetArgv() if arg_r.AtEnd() else arg_r.Rest() #util.log('user_argv %s', user_argv) status, opt_char, optarg, optind = _GetOpts(spec, user_argv, optind, self.errfmt) # Bug fix: bash-completion uses a *local* OPTIND ! Not global. state.SetStringDynamic(self.mem, 'OPTARG', optarg) state.SetStringDynamic(self.mem, 'OPTIND', str(optind)) if match.IsValidVarName(var_name): state.SetStringDynamic(self.mem, var_name, opt_char) else: # NOTE: The builtin has PARTIALLY filed. This happens in all shells # except mksh. raise error.Usage('got invalid variable name %r' % var_name, span_id=var_spid) return status
def Run(self, cmd_val): argv = cmd_val.argv[1:] arg_r = args.Reader(argv) arg = COMPADJUST_SPEC.Parse(arg_r) var_names = arg_r.Rest() # Output variables to set for name in var_names: # Ironically we could complete these if name not in ['cur', 'prev', 'words', 'cword']: raise error.Usage('Invalid output variable name %r' % name) #print(arg) # TODO: How does the user test a completion function programmatically? Set # COMP_ARGV? val = self.mem.GetVar('COMP_ARGV') if val.tag != value_e.MaybeStrArray: raise error.Usage("COMP_ARGV should be an array") comp_argv = val.strs # These are the ones from COMP_WORDBREAKS that we care about. The rest occur # "outside" of words. break_chars = [':', '='] if arg.s: # implied break_chars.remove('=') # NOTE: The syntax is -n := and not -n : -n =. omit_chars = arg.n or '' for c in omit_chars: if c in break_chars: break_chars.remove(c) # argv adjusted according to 'break_chars'. adjusted_argv = [] for a in comp_argv: completion.AdjustArg(a, break_chars, adjusted_argv) if 'words' in var_names: state.SetArrayDynamic(self.mem, 'words', adjusted_argv) n = len(adjusted_argv) cur = adjusted_argv[-1] prev = '' if n < 2 else adjusted_argv[-2] if arg.s: if cur.startswith( '--') and '=' in cur: # Split into flag name and value prev, cur = cur.split('=', 1) split = 'true' else: split = 'false' # Do NOT set 'split' without -s. Caller might not have declared it. # Also does not respect var_names, because we don't need it. state.SetStringDynamic(self.mem, 'split', split) if 'cur' in var_names: state.SetStringDynamic(self.mem, 'cur', cur) if 'prev' in var_names: state.SetStringDynamic(self.mem, 'prev', prev) if 'cword' in var_names: # Same weird invariant after adjustment state.SetStringDynamic(self.mem, 'cword', str(n - 1)) return 0
def Fail(self): # type: () -> None """On failure, reset OPTARG.""" state.SetStringDynamic(self.mem, 'OPTARG', '')
def SetArg(self, optarg): # type: (str) -> None """Set OPTARG.""" state.SetStringDynamic(self.mem, 'OPTARG', optarg)
def IncIndex(self): # type: () -> None """Increment OPTIND.""" # Note: bash-completion uses a *local* OPTIND ! Not global. assert self._optind != -1 state.SetStringDynamic(self.mem, 'OPTIND', str(self._optind + 1))
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int arg, i = READ_SPEC.ParseCmdVal(cmd_val) names = cmd_val.argv[i:] if arg.n is not None: # read a certain number of bytes stdin = sys.stdin.fileno() try: name = names[0] except IndexError: name = 'REPLY' # default variable name s = "" if sys.stdin.isatty(): # set stdin to read in unbuffered mode orig_attrs = termios.tcgetattr(stdin) attrs = termios.tcgetattr(stdin) # disable canonical (buffered) mode # see `man termios` for an extended discussion attrs[3] &= ~termios.ICANON try: termios.tcsetattr(stdin, termios.TCSANOW, attrs) # posix.read always returns a single character in unbuffered mode while arg.n > 0: s += posix.read(stdin, 1) arg.n -= 1 finally: termios.tcsetattr(stdin, termios.TCSANOW, orig_attrs) else: s_len = 0 while arg.n > 0: buf = posix.read(stdin, arg.n) # EOF if buf == '': break arg.n -= len(buf) s += buf state.SetLocalString(self.mem, name, s) # NOTE: Even if we don't get n bytes back, there is no error? return 0 if not names: names.append('REPLY') # leftover words assigned to the last name if arg.a: max_results = 0 # no max else: max_results = len(names) # We have to read more than one line if there is a line continuation (and # it's not -r). parts = [] join_next = False while True: line = ReadLineFromStdin() #log('LINE %r', line) if not line: # EOF status = 1 break if line.endswith('\n'): # strip trailing newline line = line[:-1] status = 0 else: # odd bash behavior: fail even if we can set variables. status = 1 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 if arg.a: state.SetArrayDynamic(self.mem, arg.a, parts) else: for i in xrange(max_results): try: s = parts[i] except IndexError: s = '' # if there are too many variables #log('read: %s = %s', names[i], s) state.SetStringDynamic(self.mem, names[i], s) 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() 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
def Run(self, cmd_val): # type: (cmd_value__Argv) -> int """ printf: printf [-v var] format [argument ...] """ arg_r = args.Reader(cmd_val.argv, spids=cmd_val.arg_spids) arg_r.Next() # skip argv[0] arg, _ = PRINTF_SPEC.Parse(arg_r) 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) p = _FormatStringParser(lexer) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = p.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() self.parse_cache[fmt] = parts if 0: print() for part in parts: part.PrettyPrint() print() out = [] arg_index = 0 num_args = len(varargs) while True: for part in parts: if isinstance(part, printf_part.Literal): token = part.token if token.id == Id.Format_EscapedPercent: s = '%' else: s = word_compile.EvalCStringToken(token.id, token.val) out.append(s) elif isinstance(part, printf_part.Percent): try: s = varargs[arg_index] word_spid = spids[arg_index] except IndexError: s = '' word_spid = runtime.NO_SPID typ = part.type.val if typ == 's': if part.precision: precision = int(part.precision.val) s = s[:precision] # truncate elif typ == 'q': s = string_ops.ShellQuoteOneLine(s) elif typ in 'diouxX': try: d = int(s) except ValueError: if len(s) >= 2 and s[0] in '\'"': # TODO: utf-8 decode s[1:] to be more correct. Probably # depends on issue #366, a utf-8 library. d = ord(s[1]) else: # This works around the fact that in the arg recycling case, you have no spid. if word_spid == runtime.NO_SPID: self.errfmt.Print( "printf got invalid number %r for this substitution", s, span_id=part.type.span_id) else: self.errfmt.Print( "printf got invalid number %r", s, span_id=word_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 = '%o' % d elif typ == 'x': s = '%x' % d elif typ == 'X': s = '%X' % d else: raise AssertionError() else: raise AssertionError() if part.width: width = int(part.width.val) if part.flag: flag = part.flag.val if flag == '-': s = s.ljust(width, ' ') elif flag == '0': s = s.rjust(width, '0') else: pass else: s = s.rjust(width, ' ') out.append(s) arg_index += 1 else: raise AssertionError() 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: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): raise args.UsageError('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: sys.stdout.write(result) return 0
def _Read(self, arg, names): # type: (arg_types.read, List[str]) -> int 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 stdin_fd = self.stdin.fileno() s = self._ReadN(stdin_fd, arg.n) state.SetStringDynamic(self.mem, name, s) # Did we read all the bytes we wanted? return 0 if len(s) == arg.n else 1 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
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) arena.PushSource(source.ArgvWord(fmt_spid)) try: parts = parser.Parse() except error.Parse as e: self.errfmt.PrettyPrintError(e) return 2 # parse error finally: arena.PopSource() 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_, value = 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, value) 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: var_name = arg.v # Notes: # - bash allows a[i] here (as in unset and ${!x}), but we haven't # implemented it. # - TODO: get the span_id for arg.v! if not match.IsValidVarName(var_name): e_usage('got invalid variable name %r' % var_name) state.SetStringDynamic(self.mem, var_name, result) else: mylib.Stdout().write(result) return 0