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


30

我尝试了一下which cd,但没有给出路径,而是返回了退出代码1(已选中echo $?)。coreutil cd本身正在工作,因此可执行文件应该在那里,对吗?我也运行了findfor cd,但是没有显示可执行文件。那如何实现呢?

更新:

我不知道是否应该在其他帖子中问这个问题,但是由于我认为这很好,因此我正在扩展(?)帖子...所以答案实际上很简单,没有可执行文件,因为它是一个内置文件—但是我发现一些内置文件(Fedora中的bash外壳)具有可执行文件!所以内置->没有可执行文件不是我想的吗?也许是一个解释什么是内置函数(内置命令?)的答案,实际上是这里的问题,而不是更多地关注cd……以前发布的一些好的链接表明内置函数不是程序……它们是什么?它们如何工作?它们只是外壳程序的功能还是线程?


1
阅读答案。建议使用type命令
c0rp

7
有关为何cd需要内置的内容,请参见以下问答:为什么cd不是程序?而这个为什么type要比which为什么不使用“哪个”呢?那要用什么呢?
terdon

这里有类似的问题:askubuntu.com/q/613470/178596
Wilf

Answers:


46

命令cd不能是可执行文件

在外壳程序中,cd用于“进入另一个目录”或更正式地用于更改当前工作目录(CWD)。不可能将其实现为外部命令:

该目录属于一个进程

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

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

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

Shell内置命令

因此,对于的任务运行外部命令没有任何意义cd。该命令cd需要将更改应用于当前正在运行的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

1
很好的解释!
SaltyNuts 2014年

3
比当前接受的答案好得多,这解释了为什么 cd内置了shell。
莉莉·钟

28

cd是内置于POSIX的 shell:

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

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

尽管这并没有明确说明它必须是内置的,但该规范在以下描述中cd继续说:

由于cd影响当前的shell执行环境,因此它总是作为shell常规内置文件提供。

bash手册

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

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

我想您可以想到一种cd不必内置的体系结构。但是,您必须了解内置的含义。如果您在外壳程序中编写特殊代码以对某条命令执行某些操作,则您将接近内置函数。您做得越多,拥有内置功能越好。

例如,您可以让外壳程序具有IPC与子进程进行通信,并且将有一个cd程序来检查目录的存在以及您是否具有访问权限,然后与外壳程序进行通信以告诉它更改其目录。目录。但是,您必须检查与您通信的进程是否是子进程(或仅与子进程进行特殊的通信方式,例如特殊的文件描述符,共享内存等),以及该进程是否确实存在。运行受信任的cd程序或其他内容。那就是一堆蠕虫。

或者,您可以使用一个cd程序来进行chdir系统调用,然后使用所有当前环境变量将新的外壳程序应用于新外壳程序,然后在完成后杀死其父外壳程序(以某种方式)。1个

更糟糕的是,您甚至可能拥有一个进程可以更改其他进程环境的系统(我认为从技术上讲,您可以使用调试器来完成此任务)。但是,这样的系统将非常非常脆弱。

您会发现自己添加了越来越多的代码来保护此类方法,并且简单地将其内置即可。


某些东西是可执行文件并不妨碍它成为内置文件。例子:

echotest

echotest被POSIX授权的实用工具(/bin/echo/bin/test)。但是几乎每个流行的外壳都有内置的echo和内置的test。同样,kill也内置了作为程序可用的内置函数。其他包括:

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

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

从技术上讲,没有什么可以阻止您提供既是外壳又将每个命令都内置的操作系统。接近这个极端的是整体式BusyBox。BusyBox是一个二进制文件,它(取决于调用的名称)可以充当240多个程序中的任何一个,包括Almquist Shell(ash)。如果您PATH在运行BusyBox时取消设置ash,则仍可访问BusyBox中可用的程序,而无需指定PATH。它们几乎是shell内置的,除了shell本身是BusyBox的某种内置。


案例研究:Debian Almquist Shell(dash

如果您查看dash源代码,那么执行线程就是这样的(当然,使用管道和其他内容时还会涉及其他功能):

main→交通cmdloop→交通evaltree→交通evalcommand

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行为的优秀文章。特别是:

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


4
请注意,您可以使用以下内容获得cd帮助文本help cd(所有shell内置命令的内容均相同)
Sylvain Pineau 2014年

尽管我已经链接到bash手册,但@SylvainPineau仍然建议不要将其应用于其他shell,例如zsh。
muru 2014年

确实help是bash内置函数(对于zsh,是run-help cd
Sylvain Pineau 2014年

POSIX规范中的链接描述没有明确指出cd必须是内置的Shell ...,而是基于进程属性及其在UNIX中cd作为内置的Shell 的传输方式是唯一简单的实现。请参阅Volker Siegel回复
pabouk

@pabouk确实(它称为实用程序),然后继续说:“由于cd影响当前的shell执行环境,因此它总是作为shell常规内置文件提供。”
穆鲁

8

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

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


7

来自man which

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

从我们的描述中可以看出which,它只是检查PATH。因此,如果您实现了bash function,它将什么也没显示。最好将typeCommand与一起使用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,位于内部bashman bash外壳程序命令一节中所述的所有bash内置函数

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.


1
也许应该更多地强调:不要使用which,使用type
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.