为什么 `which` 命令对 `cd` 不起作用?我也找不到 `cd` 的可执行文件!

为什么 `which` 命令对 `cd` 不起作用?我也找不到 `cd` 的可执行文件!

我试过了which cd,它没有给出路径,而是返回了退出代码 1(用 检查echo $?)。 coreutilcd本身正在运行,所以可执行文件应该在那里,对吧? 我还运行了findfor cd,但没有显示可执行文件。 那么它是如何实现的?

更新:

我不知道是否应该在另一篇文章中提出这个问题,但因为我觉得在这里问这个问题很好,所以我扩展了这篇文章……所以答案其实很简单,没有可执行文件——因为它是一个内置程序——但我发现一些内置程序(Fedora 中的 bash shell)有可执行文件!所以我认为内置程序 -> 没有可执行文件是不对的?也许答案可以解释内置程序实际上是什么(内置命令?),这实际上是这里的问题,而不是更多地关注cd……之前发布的一些好链接表明内置程序不是程序……那么它们是什么?它们是如何工作的?它们只是 shell 的函数或线程吗?

答案1

命令cd不能是可执行文件

在 shell 中,cd用于“进入另一个目录”或者更正式地说,改变当前工作目录 (CWD). 无法将其作为外部命令实现:

该目录属于某个进程

当前工作目录是用于解释相对路径以获取可用于访问文件的完整路径的目录。
相对路径在许多地方使用,并且一个进程中的解释不应影响另一个进程。
因此,每个进程都有自己的当前工作目录。

cd是关于改变shell进程的当前工作目录,例如bash

如果它是外部命令,即路径中的可执行文件,则运行该可执行文件将创建一个具有其自己的工作目录的进程,而不会影响当前 shell 的工作目录。即使外部命令会更改其目录,当外部进程退出时,该更改也会消失。

Shell 内建命令

因此,对于 的任务来说,运行外部命令是没有意义的cd。该命令cd需要对当前正在运行的 shell 进程应用更改。

要做到这一点,“内置命令”壳。

内置命令是行为类似于外部命令的命令,但是在 shell 中实现(所以cd不是 coreutils 的一部分)。这允许命令更改 shell 本身的状态,在本例中,调用chdir()see(参见man 2 chdir(英文):

关于which

现在,标题问题的答案很简单:
可执行命令which无法告诉我们 cd 是一个内置命令,因为可执行命令对内置命令一无所知。

选择type -a

作为的替代方案which,您可以使用type -a;它可以看到可执行命令和内置命令;此外,它还可以看到别名和函数 - 也在 shell 中实现:

$ type -a cd
cd is a shell builtin
$ type -a type
type is a shell builtin
$ type -a which
which is /usr/bin/which
which is /bin/which

答案2

cdPOSIX 授权shell 内置命令:

如果简单命令产生命令名称和可选参数列表,则应执行以下操作:

  1. 如果命令名称不包含任何斜杠,则将按以下序列执行第一个成功步骤:
    ...
    • 如果命令名称与下表列出的实用程序名称匹配,则应调用该实用程序。...
      ...
      cd
    • 否则,应使用 PATH 搜索命令...

虽然没有明确说明它必须是内置的,但规范继续说,描述cd

由于 cd 会影响当前 shell 执行环境,因此它始终作为 shell 常规内置命令提供。

来自bash手动的

以下 shell 内置命令继承自 Bourne Shell。这些命令按照 POSIX 标准规定的方式实现
。...

cd
       cd [-L|[-P [-e]]] [directory]

我想您可以想到一种cd不需要内置的架构。但是,您必须了解内置的含义。如果您在 shell 中编写特殊代码来执行某些命令,那么您就接近拥有内置了。您做的越多,拥有内置就越好。

例如,您可以让 shell 使用 IPC 与子进程通信,并且会有一个cd程序检查目录是否存在以及您是否有权访问该目录,然后与 shell 通信以告诉它更改其目录。但是,您必须检查与您通信的进程是否是子进程(或者只与子进程进行特殊的通信,例如特殊的文件描述符、共享内存等),以及该进程是否确实在运行受信任的程序cd或其他程序。这真是一大麻烦。

或者你可以有一个cd程序,让chdir系统调用,并启动一个新 shell,将所有当前环境变量应用到新 shell,然后在完成后终止其父 shell(以某种方式)。1

更糟糕的是,你甚至可能有一个系统,其中的进程改变其他进程的环境(我认为从技术上来说,你可以用调试器来实现这一点)。然而,这样的系统会非常非常易受伤害的。

您会发现自己添加了越来越多的代码来保护这些方法,而将其作为内置代码则要简单得多。


即使某个东西是可执行文件,也不妨碍它成为内置文件。例如:

echotest

echotest是 POSIX 强制要求的实用程序(/bin/echo/bin/test)。然而,几乎每个流行的 shell 都有一个内置的echotest。同样,kill也是可作为程序使用的内置程序。其他包括:

  • sleep(不太常见)
  • time
  • false
  • true
  • printf

但是,有些情况下,命令只能是内置命令。其中之一就是cd。通常,如果未指定完整路径,并且命令名称与内置命令的名称匹配,则会调用适合该命令的函数。根据 shell,内置命令和可执行文件的行为可能不同(这尤其适用于一个问题echo, 其中有截然不同的行为。如果您想要确定行为,最好使用完整路径调用可执行文件,并设置类似的变量POSIXLY_CORRECT(即使这样也没有真正的保证)。

从技术上讲,没有什么可以阻止你提供一个既是 shell 又有内置命令的 OS。接近这个极端的是单片忙碌盒子。BusyBox 是一个二进制文件,它(取决于它的名称)可以表现为超过 240 个项目,包括 Almquist Shell ( ash)。如果您PATH在运行 BusyBox 时取消设置ash,您仍然可以访问 BusyBox 中可用的程序,而无需指定PATH。它们接近于 shell 内置程序,只是 shell 本身是 BusyBox 的一种内置程序。


案例分析:Debian Almquist Shell(dash

如果你看一下dash源代码,执行线程是这样的(当然,在使用管道和其他东西时还涉及附加功能):

maincmdloopevaltreeevalcommand

evalcommand然后使用findcommand确定命令是什么。如果是内置命令,然后

 case CMDBUILTIN:
     if (spclbltin > 0 || argc == 0) {
         poplocalvars(1);
         if (execcmd && argc > 1)
             listsetvar(varlist.list, VEXPORT);
     }
     if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
         if (exception == EXERROR && spclbltin <= 0) {
             FORCEINTON;
             break;

cmdentry.u.cmdstructstruct builtincmd),其中一个成员是函数指针,其签名典型为main(int, char **)evalbltin函数调用(取决于内置函数是否是eval命令(或不是命令)evalcmd或者这个函数指针。实际函数在各种源文件中定义。echo例如

int
echocmd(int argc, char **argv)
{
    int nonl;

    nonl = *++argv ? equal(*argv, "-n") : 0;
    argv += nonl;

    do {
        int c;

        if (likely(*argv))
            nonl += print_escape_str("%s", NULL, NULL, *argv++);
        if (nonl > 0)
            break;

        c = *argv ? ' ' : '\n';
        out1c(c);
    } while (*argv);
    return 0;
}

本节中所有源代码链接均基于行号,因此它们可能会发生变化,恕不另行通知。


1 POSIX 系统确实有一个cd可执行文件


边注:

有很多关于 Unix 和 Linux 的优秀帖子涉及 shell 行为。特别是:

如果你还没有注意到到目前为止列出的问题中的规律,那么几乎所有问题都涉及斯蒂芬·查泽拉斯

答案3

man which

如果其参数是作为严格符合 POSIX 的 shell 中的命令给出的,则返回将在当前环境中执行的文件(或链接)的路径名。它通过在 PATH 中搜索与参数名称匹配的可执行文件来实现此目的。它不遵循符号链接。

从描述中我们可以看出which,它只是检查PATH。因此,如果您实施了一些bash function,它不会显示任何内容。最好type与 命令一起使用which

例如在 Ubuntu 中ls别名为 的命令ls --color=auto

$ type ls
ls is aliased to `ls --color=auto'

$ which ls
/bin/ls

如果你实现测试功能hello

$ function hello() { for i in {1,2,3}; do echo Hello $i;done }
$ which hello

which什么也没显示。但是type

$ type hello
hello is a function
hello () 
{ 
    for i in {1,2,3};
    do
        echo Hello $i;
    done
}

就你的情况而言:

$ type cd
cd is a shell builtin

cd意味着shell 内置命令,它位于 内bash。所有 bash 内置命令均在 内man bashSHELL BUILTIN COMMANDS 一节中描述

SHELL BUILTIN COMMANDS
       Unless otherwise noted, each builtin command documented in this section
       as accepting options preceded by - accepts -- to signify the end of the
       options.   The  :, true, false, and test builtins do not accept options
       and do not treat -- specially.  The exit, logout, break, continue, let,
       and  shift builtins accept and process arguments beginning with - with‐
       out requiring --.  Other builtins that accept  arguments  but  are  not
       specified  as accepting options interpret arguments beginning with - as
       invalid options and require -- to prevent this interpretation.

答案4

您无法找到可执行文件,cd因为没有。

cd是你的 shell 的内部命令(例如bash)。

相关内容