| 1 | #!/usr/bin/env python2
 | 
| 2 | """
 | 
| 3 | meta_osh.py - Builtins that call back into the interpreter.
 | 
| 4 | """
 | 
| 5 | from __future__ import print_function
 | 
| 6 | 
 | 
| 7 | from _devbuild.gen import arg_types
 | 
| 8 | from _devbuild.gen.runtime_asdl import cmd_value, CommandStatus
 | 
| 9 | from _devbuild.gen.syntax_asdl import source, loc
 | 
| 10 | from _devbuild.gen.value_asdl import value
 | 
| 11 | from core import alloc
 | 
| 12 | from core import dev
 | 
| 13 | from core import error
 | 
| 14 | from core import executor
 | 
| 15 | from core import main_loop
 | 
| 16 | from core import process
 | 
| 17 | from core.error import e_usage
 | 
| 18 | from core import pyutil  # strerror
 | 
| 19 | from core import state
 | 
| 20 | from core import vm
 | 
| 21 | from data_lang import j8_lite
 | 
| 22 | from frontend import flag_util
 | 
| 23 | from frontend import consts
 | 
| 24 | from frontend import reader
 | 
| 25 | from frontend import typed_args
 | 
| 26 | from mycpp.mylib import log, print_stderr
 | 
| 27 | from pylib import os_path
 | 
| 28 | from osh import cmd_eval
 | 
| 29 | 
 | 
| 30 | import posix_ as posix
 | 
| 31 | from posix_ import X_OK  # translated directly to C macro
 | 
| 32 | 
 | 
| 33 | _ = log
 | 
| 34 | 
 | 
| 35 | from typing import Dict, List, Tuple, Optional, TYPE_CHECKING
 | 
| 36 | if TYPE_CHECKING:
 | 
| 37 |     from frontend import args
 | 
| 38 |     from frontend.parse_lib import ParseContext
 | 
| 39 |     from core import optview
 | 
| 40 |     from core import ui
 | 
| 41 |     from osh.cmd_eval import CommandEvaluator
 | 
| 42 |     from osh.cmd_parse import CommandParser
 | 
| 43 | 
 | 
| 44 | 
 | 
| 45 | class Eval(vm._Builtin):
 | 
| 46 | 
 | 
| 47 |     def __init__(
 | 
| 48 |             self,
 | 
| 49 |             parse_ctx,  # type: ParseContext
 | 
| 50 |             exec_opts,  # type: optview.Exec
 | 
| 51 |             cmd_ev,  # type: CommandEvaluator
 | 
| 52 |             tracer,  # type: dev.Tracer
 | 
| 53 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 54 |     ):
 | 
| 55 |         # type: (...) -> None
 | 
| 56 |         self.parse_ctx = parse_ctx
 | 
| 57 |         self.arena = parse_ctx.arena
 | 
| 58 |         self.exec_opts = exec_opts
 | 
| 59 |         self.cmd_ev = cmd_ev
 | 
| 60 |         self.tracer = tracer
 | 
| 61 |         self.errfmt = errfmt
 | 
| 62 | 
 | 
| 63 |     def Run(self, cmd_val):
 | 
| 64 |         # type: (cmd_value.Argv) -> int
 | 
| 65 | 
 | 
| 66 |         if cmd_val.typed_args:  # eval (mycmd)
 | 
| 67 |             rd = typed_args.ReaderForProc(cmd_val)
 | 
| 68 |             cmd = rd.PosCommand()
 | 
| 69 |             rd.Done()
 | 
| 70 |             return self.cmd_ev.EvalCommand(cmd)
 | 
| 71 | 
 | 
| 72 |         # There are no flags, but we need it to respect --
 | 
| 73 |         _, arg_r = flag_util.ParseCmdVal('eval', cmd_val)
 | 
| 74 | 
 | 
| 75 |         if self.exec_opts.simple_eval_builtin():
 | 
| 76 |             code_str, eval_loc = arg_r.ReadRequired2('requires code string')
 | 
| 77 |             if not arg_r.AtEnd():
 | 
| 78 |                 e_usage('requires exactly 1 argument', loc.Missing)
 | 
| 79 |         else:
 | 
| 80 |             code_str = ' '.join(arg_r.Rest())
 | 
| 81 |             # code_str could be EMPTY, so just use the first one
 | 
| 82 |             eval_loc = cmd_val.arg_locs[0]
 | 
| 83 | 
 | 
| 84 |         line_reader = reader.StringLineReader(code_str, self.arena)
 | 
| 85 |         c_parser = self.parse_ctx.MakeOshParser(line_reader)
 | 
| 86 | 
 | 
| 87 |         src = source.ArgvWord('eval', eval_loc)
 | 
| 88 |         with dev.ctx_Tracer(self.tracer, 'eval', None):
 | 
| 89 |             with alloc.ctx_SourceCode(self.arena, src):
 | 
| 90 |                 return main_loop.Batch(self.cmd_ev,
 | 
| 91 |                                        c_parser,
 | 
| 92 |                                        self.errfmt,
 | 
| 93 |                                        cmd_flags=cmd_eval.RaiseControlFlow)
 | 
| 94 | 
 | 
| 95 | 
 | 
| 96 | class Source(vm._Builtin):
 | 
| 97 | 
 | 
| 98 |     def __init__(
 | 
| 99 |             self,
 | 
| 100 |             parse_ctx,  # type: ParseContext
 | 
| 101 |             search_path,  # type: state.SearchPath
 | 
| 102 |             cmd_ev,  # type: CommandEvaluator
 | 
| 103 |             fd_state,  # type: process.FdState
 | 
| 104 |             tracer,  # type: dev.Tracer
 | 
| 105 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 106 |             loader,  # type: pyutil._ResourceLoader
 | 
| 107 |     ):
 | 
| 108 |         # type: (...) -> None
 | 
| 109 |         self.parse_ctx = parse_ctx
 | 
| 110 |         self.arena = parse_ctx.arena
 | 
| 111 |         self.search_path = search_path
 | 
| 112 |         self.cmd_ev = cmd_ev
 | 
| 113 |         self.fd_state = fd_state
 | 
| 114 |         self.tracer = tracer
 | 
| 115 |         self.errfmt = errfmt
 | 
| 116 |         self.loader = loader
 | 
| 117 | 
 | 
| 118 |         self.mem = cmd_ev.mem
 | 
| 119 | 
 | 
| 120 |     def Run(self, cmd_val):
 | 
| 121 |         # type: (cmd_value.Argv) -> int
 | 
| 122 |         attrs, arg_r = flag_util.ParseCmdVal('source', cmd_val)
 | 
| 123 |         arg = arg_types.source(attrs.attrs)
 | 
| 124 | 
 | 
| 125 |         path = arg_r.Peek()
 | 
| 126 |         if path is None:
 | 
| 127 |             e_usage('missing required argument', loc.Missing)
 | 
| 128 |         arg_r.Next()
 | 
| 129 | 
 | 
| 130 |         if arg.builtin:
 | 
| 131 |             try:
 | 
| 132 |                 path = os_path.join("stdlib", path)
 | 
| 133 |                 contents = self.loader.Get(path)
 | 
| 134 |             except (IOError, OSError):
 | 
| 135 |                 self.errfmt.Print_(
 | 
| 136 |                     'source --builtin %r failed: No such builtin file' % path,
 | 
| 137 |                     blame_loc=cmd_val.arg_locs[2])
 | 
| 138 |                 return 2
 | 
| 139 | 
 | 
| 140 |             line_reader = reader.StringLineReader(contents, self.arena)
 | 
| 141 |             c_parser = self.parse_ctx.MakeOshParser(line_reader)
 | 
| 142 |             return self._Exec(cmd_val, arg_r, path, c_parser)
 | 
| 143 | 
 | 
| 144 |         else:
 | 
| 145 |             # 'source' respects $PATH
 | 
| 146 |             resolved = self.search_path.LookupOne(path, exec_required=False)
 | 
| 147 |             if resolved is None:
 | 
| 148 |                 resolved = path
 | 
| 149 | 
 | 
| 150 |             try:
 | 
| 151 |                 # Shell can't use descriptors 3-9
 | 
| 152 |                 f = self.fd_state.Open(resolved)
 | 
| 153 |             except (IOError, OSError) as e:
 | 
| 154 |                 self.errfmt.Print_('source %r failed: %s' %
 | 
| 155 |                                    (path, pyutil.strerror(e)),
 | 
| 156 |                                    blame_loc=cmd_val.arg_locs[1])
 | 
| 157 |                 return 1
 | 
| 158 | 
 | 
| 159 |             line_reader = reader.FileLineReader(f, self.arena)
 | 
| 160 |             c_parser = self.parse_ctx.MakeOshParser(line_reader)
 | 
| 161 | 
 | 
| 162 |             with process.ctx_FileCloser(f):
 | 
| 163 |                 return self._Exec(cmd_val, arg_r, path, c_parser)
 | 
| 164 | 
 | 
| 165 |     def _Exec(self, cmd_val, arg_r, path, c_parser):
 | 
| 166 |         # type: (cmd_value.Argv, args.Reader, str, CommandParser) -> int
 | 
| 167 |         call_loc = cmd_val.arg_locs[0]
 | 
| 168 | 
 | 
| 169 |         # A sourced module CAN have a new arguments array, but it always shares
 | 
| 170 |         # the same variable scope as the caller.  The caller could be at either a
 | 
| 171 |         # global or a local scope.
 | 
| 172 | 
 | 
| 173 |         # TODO: I wonder if we compose the enter/exit methods more easily.
 | 
| 174 | 
 | 
| 175 |         with dev.ctx_Tracer(self.tracer, 'source', cmd_val.argv):
 | 
| 176 |             source_argv = arg_r.Rest()
 | 
| 177 |             with state.ctx_Source(self.mem, path, source_argv):
 | 
| 178 |                 with state.ctx_ThisDir(self.mem, path):
 | 
| 179 |                     src = source.SourcedFile(path, call_loc)
 | 
| 180 |                     with alloc.ctx_SourceCode(self.arena, src):
 | 
| 181 |                         try:
 | 
| 182 |                             status = main_loop.Batch(
 | 
| 183 |                                 self.cmd_ev,
 | 
| 184 |                                 c_parser,
 | 
| 185 |                                 self.errfmt,
 | 
| 186 |                                 cmd_flags=cmd_eval.RaiseControlFlow)
 | 
| 187 |                         except vm.IntControlFlow as e:
 | 
| 188 |                             if e.IsReturn():
 | 
| 189 |                                 status = e.StatusCode()
 | 
| 190 |                             else:
 | 
| 191 |                                 raise
 | 
| 192 | 
 | 
| 193 |         return status
 | 
| 194 | 
 | 
| 195 | 
 | 
| 196 | def _PrintFreeForm(row):
 | 
| 197 |     # type: (Tuple[str, str, Optional[str]]) -> None
 | 
| 198 |     name, kind, resolved = row
 | 
| 199 | 
 | 
| 200 |     if kind == 'file':
 | 
| 201 |         what = resolved
 | 
| 202 |     elif kind == 'alias':
 | 
| 203 |         what = ('an alias for %s' %
 | 
| 204 |                 j8_lite.EncodeString(resolved, unquoted_ok=True))
 | 
| 205 |     else:  # builtin, function, keyword
 | 
| 206 |         what = 'a shell %s' % kind
 | 
| 207 | 
 | 
| 208 |     # TODO: Should also print haynode
 | 
| 209 | 
 | 
| 210 |     print('%s is %s' % (name, what))
 | 
| 211 | 
 | 
| 212 |     # if kind == 'function':
 | 
| 213 |     #   bash is the only shell that prints the function
 | 
| 214 | 
 | 
| 215 | 
 | 
| 216 | def _PrintEntry(arg, row):
 | 
| 217 |     # type: (arg_types.type, Tuple[str, str, Optional[str]]) -> None
 | 
| 218 | 
 | 
| 219 |     _, kind, resolved = row
 | 
| 220 |     assert kind is not None
 | 
| 221 | 
 | 
| 222 |     if arg.t:  # short string
 | 
| 223 |         print(kind)
 | 
| 224 | 
 | 
| 225 |     elif arg.p:
 | 
| 226 |         #log('%s %s %s', name, kind, resolved)
 | 
| 227 |         if kind == 'file':
 | 
| 228 |             print(resolved)
 | 
| 229 | 
 | 
| 230 |     else:  # free-form text
 | 
| 231 |         _PrintFreeForm(row)
 | 
| 232 | 
 | 
| 233 | 
 | 
| 234 | class Command(vm._Builtin):
 | 
| 235 |     """'command ls' suppresses function lookup."""
 | 
| 236 | 
 | 
| 237 |     def __init__(
 | 
| 238 |             self,
 | 
| 239 |             shell_ex,  # type: vm._Executor
 | 
| 240 |             funcs,  # type: Dict[str, value.Proc]
 | 
| 241 |             aliases,  # type: Dict[str, str]
 | 
| 242 |             search_path,  # type: state.SearchPath
 | 
| 243 |     ):
 | 
| 244 |         # type: (...) -> None
 | 
| 245 |         self.shell_ex = shell_ex
 | 
| 246 |         self.funcs = funcs
 | 
| 247 |         self.aliases = aliases
 | 
| 248 |         self.search_path = search_path
 | 
| 249 | 
 | 
| 250 |     def Run(self, cmd_val):
 | 
| 251 |         # type: (cmd_value.Argv) -> int
 | 
| 252 | 
 | 
| 253 |         # accept_typed_args=True because we invoke other builtins
 | 
| 254 |         attrs, arg_r = flag_util.ParseCmdVal('command',
 | 
| 255 |                                              cmd_val,
 | 
| 256 |                                              accept_typed_args=True)
 | 
| 257 |         arg = arg_types.command(attrs.attrs)
 | 
| 258 | 
 | 
| 259 |         argv, locs = arg_r.Rest2()
 | 
| 260 | 
 | 
| 261 |         if arg.v or arg.V:
 | 
| 262 |             status = 0
 | 
| 263 |             for argument in argv:
 | 
| 264 |                 r = _ResolveName(argument, self.funcs, self.aliases,
 | 
| 265 |                                  self.search_path, False)
 | 
| 266 |                 if len(r):
 | 
| 267 |                     # command -v prints the name (-V is more detailed)
 | 
| 268 |                     # Print it only once.
 | 
| 269 |                     row = r[0]
 | 
| 270 |                     name, _, _ = row
 | 
| 271 |                     if arg.v:
 | 
| 272 |                         print(name)
 | 
| 273 |                     else:
 | 
| 274 |                         _PrintFreeForm(row)
 | 
| 275 |                 else:
 | 
| 276 |                     # match bash behavior by printing to stderr
 | 
| 277 |                     print_stderr('%s: not found' % argument)
 | 
| 278 |                     status = 1  # nothing printed, but we fail
 | 
| 279 | 
 | 
| 280 |             return status
 | 
| 281 | 
 | 
| 282 |         cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.typed_args,
 | 
| 283 |                                   cmd_val.pos_args, cmd_val.named_args,
 | 
| 284 |                                   cmd_val.block_arg)
 | 
| 285 | 
 | 
| 286 |         # If we respected do_fork here instead of passing True, the case
 | 
| 287 |         # 'command date | wc -l' would take 2 processes instead of 3.  But no other
 | 
| 288 |         # shell does that, and this rare case isn't worth the bookkeeping.
 | 
| 289 |         # See test/syscall
 | 
| 290 |         cmd_st = CommandStatus.CreateNull(alloc_lists=True)
 | 
| 291 | 
 | 
| 292 |         run_flags = executor.DO_FORK | executor.NO_CALL_PROCS
 | 
| 293 |         if arg.p:
 | 
| 294 |             run_flags |= executor.USE_DEFAULT_PATH
 | 
| 295 | 
 | 
| 296 |         return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st, run_flags)
 | 
| 297 | 
 | 
| 298 | 
 | 
| 299 | def _ShiftArgv(cmd_val):
 | 
| 300 |     # type: (cmd_value.Argv) -> cmd_value.Argv
 | 
| 301 |     return cmd_value.Argv(cmd_val.argv[1:], cmd_val.arg_locs[1:],
 | 
| 302 |                           cmd_val.typed_args, cmd_val.pos_args,
 | 
| 303 |                           cmd_val.named_args, cmd_val.block_arg)
 | 
| 304 | 
 | 
| 305 | 
 | 
| 306 | class Builtin(vm._Builtin):
 | 
| 307 | 
 | 
| 308 |     def __init__(self, shell_ex, errfmt):
 | 
| 309 |         # type: (vm._Executor, ui.ErrorFormatter) -> None
 | 
| 310 |         self.shell_ex = shell_ex
 | 
| 311 |         self.errfmt = errfmt
 | 
| 312 | 
 | 
| 313 |     def Run(self, cmd_val):
 | 
| 314 |         # type: (cmd_value.Argv) -> int
 | 
| 315 | 
 | 
| 316 |         if len(cmd_val.argv) == 1:
 | 
| 317 |             return 0  # this could be an error in strict mode?
 | 
| 318 | 
 | 
| 319 |         name = cmd_val.argv[1]
 | 
| 320 | 
 | 
| 321 |         # Run regular builtin or special builtin
 | 
| 322 |         to_run = consts.LookupNormalBuiltin(name)
 | 
| 323 |         if to_run == consts.NO_INDEX:
 | 
| 324 |             to_run = consts.LookupSpecialBuiltin(name)
 | 
| 325 |         if to_run == consts.NO_INDEX:
 | 
| 326 |             location = cmd_val.arg_locs[1]
 | 
| 327 |             if consts.LookupAssignBuiltin(name) != consts.NO_INDEX:
 | 
| 328 |                 # NOTE: There's a similar restriction for 'command'
 | 
| 329 |                 self.errfmt.Print_("Can't run assignment builtin recursively",
 | 
| 330 |                                    blame_loc=location)
 | 
| 331 |             else:
 | 
| 332 |                 self.errfmt.Print_("%r isn't a shell builtin" % name,
 | 
| 333 |                                    blame_loc=location)
 | 
| 334 |             return 1
 | 
| 335 | 
 | 
| 336 |         cmd_val2 = _ShiftArgv(cmd_val)
 | 
| 337 |         return self.shell_ex.RunBuiltin(to_run, cmd_val2)
 | 
| 338 | 
 | 
| 339 | 
 | 
| 340 | class RunProc(vm._Builtin):
 | 
| 341 | 
 | 
| 342 |     def __init__(self, shell_ex, procs, errfmt):
 | 
| 343 |         # type: (vm._Executor, Dict[str, value.Proc], ui.ErrorFormatter) -> None
 | 
| 344 |         self.shell_ex = shell_ex
 | 
| 345 |         self.procs = procs
 | 
| 346 |         self.errfmt = errfmt
 | 
| 347 | 
 | 
| 348 |     def Run(self, cmd_val):
 | 
| 349 |         # type: (cmd_value.Argv) -> int
 | 
| 350 |         _, arg_r = flag_util.ParseCmdVal('runproc',
 | 
| 351 |                                          cmd_val,
 | 
| 352 |                                          accept_typed_args=True)
 | 
| 353 |         argv, locs = arg_r.Rest2()
 | 
| 354 | 
 | 
| 355 |         if len(argv) == 0:
 | 
| 356 |             raise error.Usage('requires arguments', loc.Missing)
 | 
| 357 | 
 | 
| 358 |         name = argv[0]
 | 
| 359 |         if name not in self.procs:
 | 
| 360 |             self.errfmt.PrintMessage('runproc: no proc named %r' % name)
 | 
| 361 |             return 1
 | 
| 362 | 
 | 
| 363 |         cmd_val2 = cmd_value.Argv(argv, locs, cmd_val.typed_args,
 | 
| 364 |                                   cmd_val.pos_args, cmd_val.named_args,
 | 
| 365 |                                   cmd_val.block_arg)
 | 
| 366 | 
 | 
| 367 |         cmd_st = CommandStatus.CreateNull(alloc_lists=True)
 | 
| 368 |         return self.shell_ex.RunSimpleCommand(cmd_val2, cmd_st,
 | 
| 369 |                                               executor.DO_FORK)
 | 
| 370 | 
 | 
| 371 | 
 | 
| 372 | def _ResolveName(
 | 
| 373 |         name,  # type: str
 | 
| 374 |         funcs,  # type: Dict[str, value.Proc]
 | 
| 375 |         aliases,  # type: Dict[str, str]
 | 
| 376 |         search_path,  # type: state.SearchPath
 | 
| 377 |         do_all,  # type: bool
 | 
| 378 | ):
 | 
| 379 |     # type: (...) -> List[Tuple[str, str, Optional[str]]]
 | 
| 380 | 
 | 
| 381 |     # MyPy tuple type
 | 
| 382 |     no_str = None  # type: Optional[str]
 | 
| 383 | 
 | 
| 384 |     results = []  # type: List[Tuple[str, str, Optional[str]]]
 | 
| 385 | 
 | 
| 386 |     if name in funcs:
 | 
| 387 |         results.append((name, 'function', no_str))
 | 
| 388 | 
 | 
| 389 |     if name in aliases:
 | 
| 390 |         results.append((name, 'alias', aliases[name]))
 | 
| 391 | 
 | 
| 392 |     # See if it's a builtin
 | 
| 393 |     if consts.LookupNormalBuiltin(name) != 0:
 | 
| 394 |         results.append((name, 'builtin', no_str))
 | 
| 395 |     elif consts.LookupSpecialBuiltin(name) != 0:
 | 
| 396 |         results.append((name, 'builtin', no_str))
 | 
| 397 |     elif consts.LookupAssignBuiltin(name) != 0:
 | 
| 398 |         results.append((name, 'builtin', no_str))
 | 
| 399 | 
 | 
| 400 |     # See if it's a keyword
 | 
| 401 |     if consts.IsControlFlow(name):  # continue, etc.
 | 
| 402 |         results.append((name, 'keyword', no_str))
 | 
| 403 |     elif consts.IsKeyword(name):
 | 
| 404 |         results.append((name, 'keyword', no_str))
 | 
| 405 | 
 | 
| 406 |     # See if it's external
 | 
| 407 |     for path in search_path.LookupReflect(name, do_all):
 | 
| 408 |         if posix.access(path, X_OK):
 | 
| 409 |             results.append((name, 'file', path))
 | 
| 410 | 
 | 
| 411 |     return results
 | 
| 412 | 
 | 
| 413 | 
 | 
| 414 | class Type(vm._Builtin):
 | 
| 415 | 
 | 
| 416 |     def __init__(
 | 
| 417 |             self,
 | 
| 418 |             funcs,  # type: Dict[str, value.Proc]
 | 
| 419 |             aliases,  # type: Dict[str, str]
 | 
| 420 |             search_path,  # type: state.SearchPath
 | 
| 421 |             errfmt,  # type: ui.ErrorFormatter
 | 
| 422 |     ):
 | 
| 423 |         # type: (...) -> None
 | 
| 424 |         self.funcs = funcs
 | 
| 425 |         self.aliases = aliases
 | 
| 426 |         self.search_path = search_path
 | 
| 427 |         self.errfmt = errfmt
 | 
| 428 | 
 | 
| 429 |     def Run(self, cmd_val):
 | 
| 430 |         # type: (cmd_value.Argv) -> int
 | 
| 431 |         attrs, arg_r = flag_util.ParseCmdVal('type', cmd_val)
 | 
| 432 |         arg = arg_types.type(attrs.attrs)
 | 
| 433 | 
 | 
| 434 |         if arg.f:  # suppress function lookup
 | 
| 435 |             funcs = {}  # type: Dict[str, value.Proc]
 | 
| 436 |         else:
 | 
| 437 |             funcs = self.funcs
 | 
| 438 | 
 | 
| 439 |         status = 0
 | 
| 440 |         names = arg_r.Rest()
 | 
| 441 | 
 | 
| 442 |         if arg.P:  # -P should forces PATH search, regardless of builtin/alias/function/etc.
 | 
| 443 |             for name in names:
 | 
| 444 |                 paths = self.search_path.LookupReflect(name, arg.a)
 | 
| 445 |                 if len(paths):
 | 
| 446 |                     for path in paths:
 | 
| 447 |                         print(path)
 | 
| 448 |                 else:
 | 
| 449 |                     status = 1
 | 
| 450 |             return status
 | 
| 451 | 
 | 
| 452 |         for argument in names:
 | 
| 453 |             r = _ResolveName(argument, funcs, self.aliases, self.search_path,
 | 
| 454 |                              arg.a)
 | 
| 455 |             if arg.a:
 | 
| 456 |                 for row in r:
 | 
| 457 |                     _PrintEntry(arg, row)
 | 
| 458 |             else:
 | 
| 459 |                 if len(r):  # Just print the first one
 | 
| 460 |                     _PrintEntry(arg, r[0])
 | 
| 461 | 
 | 
| 462 |             # Error case
 | 
| 463 |             if len(r) == 0:
 | 
| 464 |                 if not arg.t:  # 'type -t' is silent in this case
 | 
| 465 |                     # match bash behavior by printing to stderr
 | 
| 466 |                     print_stderr('%s: not found' % argument)
 | 
| 467 |                 status = 1  # nothing printed, but we fail
 | 
| 468 | 
 | 
| 469 |         return status
 |