为什么“`命令不能用于`cd`? 我也找不到`cd`的可执行文件!

我尝试了which cd ,它没有给出路径,而是返回退出代码1(用echo $?检查)。 coreutil cd本身正在工作,所以可执行文件应该在那里,对吧? 我还运行了一个cdfind ,但是没有显示可执行文件。 那怎么实现呢?

更新:

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

命令cd不能是可执行文件

在shell中, cd用于“进入另一个目录”,或者更正式地用于更改curent工作目录(CWD)。 作为外部命令实现它是不可能的:

该目录属于进程

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

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

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

Shell内置命令

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

为此,它是shell的“内置命令”。

内置命令是与外部命令类似的命令,但是在shell中实现(因此cd不是coreutils的一部分)。 这允许命令改变shell本身的状态,在这种情况下调用chdir()看(参见man 2 chdir );

关于which

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

替代type -a

作为替代方案,您可以使用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 

cd是一个POSIX强制 shell内置:

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

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

      cd
    • 否则,应使用PATH搜索命令…

虽然这没有明确说明它必须是内置的,但规范接着说,在cd的描述中 :

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

bash手册 :

以下shell内置命令inheritance自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的操作系统,并且每个命令都是内置的。 接近这个极端的是单片BusyBox 。 BusyBox是一个单独的二进制文件(取决于它所调用的名称)可以表现为超过240个程序中的任何一个,包括Almquist Shell( ash )。 如果在运行BusyBox ash取消设置PATH ,则无需指定PATH即可访问BusyBox中可用的程序。 它们接近于shell内置,除了shell本身是BusyBox的内置类型。


案例研究: Debian Almquist Shell( dash

如果你看一下dash源,执行线程是这样的(当然,当使用管道和其他东西时涉及额外的function):

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.cmd是一个structstruct builtincmd ),其成员之一是一个函数指针,其签名典型为main(int, char **)evalbltin函数调用(取决于内置eval是否为eval命令) evalcmd或此函数指针。 实际function在各种源文件中定义。 例如, 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行为的优秀post。 特别是:

  • 为什么printf比echo更好?
  • 为什么不用“哪个”? 那要用什么?
  • 为什么我不能将路径名输出从一个命令重定向到“cd”?
  • cd外部命令有什么意义?
  • 将名称 - 值对添加到命令和在bash中使用env之间有区别吗?

如果你还没有注意到目前为止列出的问题中的模式,那么几乎所有问题都涉及StéphaneChazelas 。

你找不到cd的可执行文件,因为没有。

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

来自man which

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

正如我们从描述中看到的那样,它只是检查PATH 。 因此,如果您实现了一些bash function ,它将不会显示任何内容。 最好使用type命令。

例如,在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 

什么都没显示。 但是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 builtins都在man bash描述,在SHELL BUILTIN命令中

 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.