为什么不使用“哪个”呢?那要用什么呢?


328

当寻找的路径,可执行文件或检查,如果你在Unix shell中输入命令的名称会发生什么,有不同的公用事业过多(whichtypecommandwhencewherewhereiswhatishash,等)。

我们经常听到which应该避免这种情况。为什么?我们应该使用什么呢?


3
我认为大多数反对使用的观点which都假设是交互式外壳上下文。这个问题被标记为/可移植性。因此,在这种情况下,我将问题解释为“使用什么,而不是在which中找到给定名称的第一个可执行文件$PATH”。大多数答案和反对理由都which涉及别名,内建函数和函数,这些别名在大多数现实世界中的可移植外壳脚本中都具有学术意义。运行shell脚本时,不会继承本地定义的别名(除非您使用来提供别名.)。
MattBianco 2014年

5
@MattBianco是的csh(并且在大多数商业Unices which上仍然是csh脚本)~/.cshrc在非交互式时会读取。这就是为什么您会注意到csh脚本通常以开头的原因#! /bin/csh -fwhich并不是因为它旨在为您提供别名,而是因为它是的(交互)用户的工具csh。用户拥有POSIX shell command -v
斯特凡Chazelas

@rudimeier,那么除非您的shell是(t)csh(或者您不介意它没有给您正确的结果),否则typecommand -v答案始终是,请使用代替。查看答案以了解原因
斯特凡Chazelas

1
@rudimeier,((stat $(which ls)由于多种原因(缺少--,缺少引号),不仅是的用法,它是错误的which)。你会用stat -- "$(command -v ls)"。假定ls确实是在文件系统上找到的命令(而不是外壳程序的内置命令或别名功能)。which可能会给你走错了路(不,如果你输入你的shell将执行路径ls),或者给你一个别名,像其他炮弹的配置中定义...
斯特凡Chazelas

1
再次,@ rudimeier,在许多情况下,许多which实现甚至都无法给您提供ls通过查找而发现的条件$PATH(无论ls您的shell中调用了什么)。sh -c 'command -v ls',或者zsh -c 'rpm -q --whatprovides =ls'更有可能为您提供正确的答案。这里的意思是,这which是来自的破碎遗产csh
斯特凡Chazelas

Answers:


365

这是您从未想过的所有信息:

摘要

要在类似Bourne的shell脚本中获取可执行文件的路径名(有一些警告;请参见下文):

ls=$(command -v ls)

要查找给定命令是否存在:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

在交互式的类似于Bourne的shell的提示下:

type ls

which命令是C-Shell的继承传统,最好放在类似Bourne的shell中。

用例

在脚本中查找信息或在shell提示符下以交互方式查找信息之间是有区别的。

在shell提示符下,典型的用例是:该命令的行为很奇怪,我使用的是正确的命令吗?键入时究竟发生了什么mycmd?我可以进一步看看它是什么吗?

在这种情况下,您想知道在调用该命令而不实际调用该命令时外壳程序会做什么。

在shell脚本中,它往往有很大的不同。在shell脚本中,没有理由要运行命令就知道命令在哪里或什么地方。通常,您想知道的是可执行文件的路径,因此您可以从中获取更多信息(例如相对于另一个文件的路径,或者从该路径中的可执行文件的内容中读取信息)。

交互,你可能想知道所有my-cmd系统上可用的命令,在脚本中,很少如此。

大多数可用工具(通常是这种情况)已设计为可交互使用。

历史

首先有一点历史。

早期的Unix外壳直到70年代后期都没有功能或别名。仅以传统方式查找可执行文件$PATHcsh1978年左右推出的别名(虽然csh最初发布2BSD,在1979年5月),并且还的加工.cshrc,为用户定制的外壳(每一个外壳,如csh读取.cshrc,即使不是交互式像脚本)。

Bourne shell于1979年初在Unix V7中首次发布,而功能支持只是在后来才添加(SVR2中为1984),无论如何,它从来没有任何rc文件(.profile用于配置环境,而不是shell 本身)。

csh 它比Bourne Shell流行得多(因为它的语法比Bourne Shell糟糕得多),它为交互使用添加了许多更方便,更漂亮的功能。

3BSD(1980)中,为用户添加了一个whichcsh脚本csh以帮助识别可执行文件,并且与which当今许多商业Unices(例如Solaris,HP / UX,AIX或Tru64)一样,您可以找到几乎与众不同的脚本。

该脚本读取用户的脚本~/.cshrc(就像所有csh脚本一样,除非使用调用csh -f),然后在别名列表和$pathcsh基于的数组)中查找提供的命令名称$PATH

在这里,您which发现它是当时最流行的外壳程序的第一名(csh直到90年代中期仍很流行),这是它被记录在书中并仍被广泛使用的主要原因。

请注意,即使对于csh用户,该whichcsh脚本也不一定能为您提供正确的信息。它得到的别名是在中定义的~/.cshrc,而不是您稍后在提示符下定义的别名,或者例如通过打开source另一个csh文件而定义的别名,(尽管那不是一个好主意),PATH可以在中重新定义~/.cshrc

which从Bourne shell 运行该命令,仍会查找您在中定义的别名~/.cshrc,但是如果由于不使用而没有别名csh,那么仍然可能会找到正确的答案。

直到1984年,带有type内置命令的SVR2才向Bourne Shell中添加了类似的功能。它是内置的(而不是外部脚本),这意味着它可以在一定程度上为您提供正确的信息,因为它可以访问Shell的内部。

初始type命令与which脚本存在类似的问题,即如果找不到该命令,则它不会返回故障退出状态。同样,对于可执行文件,与相反which,它输出类似于的内容,ls is /bin/ls而不是类似的内容,/bin/ls这使得在脚本中使用起来不那么容易。

Unix版本8(未在野外发布)的Bourne shell将其type内置重命名为whatis。而Plan9(曾经是Unix的继任者)外壳rc(及其派生类如akangaand eswhatis也是如此。

Korn shell(以POSIX sh定义为基础的子集)于80年代中期开发,但在1988年前未广泛使用csh,它在Bourne shell之上添加了许多功能(行编辑器,别名...)。 。它添加了自己的whence内建函数(除了type),并采用了多个选项(-v以提供type类似-like的详细输出,并且-p仅查找可执行文件(而不是别名/函数...))。

巧合的是,在AT&T与伯克利之间的版权问题上出现了动荡,在80年代末90年代初出现了一些自由软件外壳实现。所有的Almquist shell(灰,将替换BSD中的Bourne shell),ksh(pdksh)bash(由FSF赞助)的公共领域实现,zsh都是在1989年至1991年之间发布的。

Ash虽说是Bourne外壳的替代品,但type直到后来(在NetBSD 1.3和FreeBSD 2.3中)才具有内置功能hash -v。OSF / 1 /bin/sh具有一个type内置函数,在OSF / 1 v3.x之前始终返回0。bash没有添加,whence而是添加了一个-p选项来type打印路径(type -pwhence -p)并-a报告所有匹配的命令。tcshwhich内置并增加了一个where表现得象命令bashtype -azsh全部拥有。

fish外壳(2005)具有type作为一个功能来实现命令。

which同时,csh脚本已从NetBSD中删除(因为它内置于tcsh中,并且在其他shell中使用不多),并且所添加的功能whereis(被调用为时whichwhereis其行为类似,which只是它仅在中查找可执行文件$PATH)。在OpenBSD和FreeBSD中,which也被更改为用C编写的仅在其中查找命令的代码$PATH

实作

which具有不同语法和行为的各种Unices上有数十种命令实现。

在Linux上(tcsh和中的内置功能除外zsh),我们发现了几种实现。例如,在最近的Debian系统上,它是一个简单的POSIX shell脚本,用于在中查找命令$PATH

busybox也有一个which命令。

有一种GNU which可能是最奢侈的一种。它尝试将whichcsh脚本的功能扩展到其他shell:您可以告诉它别名和函数是什么,以便它可以为您提供更好的答案(我相信某些Linux发行版为此设置了一些全局别名bash) 。

zsh有几个运算符可扩展到可执行文件的路径:= 文件名扩展运算符和:c历史记录扩展修饰符(此处应用于参数扩展):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zshzsh/parameters模块中,还使命令哈希表作为commands关联数组:

$ print -r -- $commands[ls]
/bin/ls

whatis实用程序(Unix V8 Bourne shell或Plan 9 rc/中的实用程序除外es)并不真正相关,因为它仅用于文档编制(使用whatis数据库,即手册页摘要)。

whereis也被同时添加,3BSD就像which它是用编写的一样C,不是csh,并且用于同时查找可执行文件,手册页和源,但不是基于当前环境。同样,这回答了不同的需求。

现在,在标准方面,POSIX指定command -v-V命令(在POSIX.2008之前,它是可选的)。UNIX指定type命令(无选项)。这是所有(wherewhichwhence不以任何标准中规定)

最新版本,type并且command -v在Linux Standard Base规范中是可选的,它解释了为什么例如某些旧版本posh(尽管基于pdksh这两个版本都没有)。command -v还被添加到某些Bourne Shell实现中(例如在Solaris上)。

今天的状态

如今的状态是,type并且command -v在所有类似Bourne的shell中都是普遍存在的(尽管,如@jarno所指出的,请注意在bash非POSIX模式下或以下Almquist shell的某些后代中的警告/错误)。tcsh是您要使用的唯一外壳程序which(因为那里没有外壳type并且which是内置的)。

在以外的炮弹tcshzshwhich只要有任何我们没有别名或函数由同名可能会告诉你给定的可执行文件的路径~/.cshrc~/.bashrc或任何shell启动文件,你不定义$PATH你的~/.cshrc。如果为它定义了别名或函数,则它可能不会告诉您有关别名或告诉您的错误信息。

如果您想通过给定名称了解所有命令,则没有任何可移植的内容。你会使用wheretcsh还是zshtype -abash或者zshwhence -a在ksh93的和其他炮弹,你可以用type结合which -a这可能工作。

推荐建议

获取可执行文件的路径

现在,要获取脚本中可执行文件的路径名,请注意以下几点:

ls=$(command -v ls)

将是这样做的标准方法。

但是有一些问题:

  • 不执行可执行文件就无法知道其路径。所有typewhichcommand -v...所有使用启发式找出路。它们遍历$PATH组件,并找到您具有执行权限的第一个非目录文件。但是,根据外壳程序的不同,在执行命令时,它们中的许多(Bourne,AT&T ksh,zsh,ash ...)将按以下顺序执行,$PATH直到execve系统调用不会返回错误为止。例如,如果$PATH包含/foo:/bar并且要执行ls,则它们将首先尝试执行,否则将/foo/ls失败/bar/ls。现在执行/foo/ls可能会因为您没有执行权限而失败,但也可能由于其他许多原因而失败,例如它不是有效的可执行文件。command -v ls将报告/foo/ls您是否具有的执行许可权/foo/ls,但是如果不是有效的可执行文件,则运行ls可能会实际运行。/bar/ls/foo/ls
  • 如果foo是内置函数,函数或别名,则command -v foo返回foo。一些炮弹一样ashpdksh或者zsh,它也可能返回foo如果$PATH包括空字符串,并且有一个可执行文件foo在当前目录下的文件。在某些情况下,您可能需要考虑到这一点。请记住,例如,内建的列表随shell实现的不同而有所变化(例如,mount有时内置于busybox sh),并且例如bash可以从环境中获取功能。
  • 如果$PATH包含相对路径组件(通常.是空字符串,都引用当前目录,但可以是任何东西),则取决于外壳程序,command -v cmd可能不会输出绝对路径。因此,在其他地方运行command -v之后,您在运行时获得的路径将不再有效cd
  • 传闻:与ksh93的外壳,如果/opt/ast/bin(尽管确切的路径可以在不同的系统,我相信会发生变化)是你$PATH,ksh93的将提供一些额外的内置函数(chmodcmpcat...),但command -v chmod将返回/opt/ast/bin/chmod即使这条道路没有按”不存在。

确定命令是否存在

要确定给定命令是否标准存在,可以执行以下操作:

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

可能要使用的地方 which

(t)csh

csh和中tcsh,您没有太多选择。在中tcsh,就像which内置一样。在中csh,这将是系统which命令,在某些情况下可能无法完成您想要的操作。

仅在某些外壳中查找命令

可能有意义的一种情况which是,如果您想知道命令的路径,而忽略bashcsh(不是tcshdash,或Bourneshell脚本中潜在的shell内建函数或函数,即没有shell的shell whence -p(如like kshzsh) ,command -ev(如yash),whatis -prcakanga)或内置脚本which(如tcshzsh)在which可用且不是csh脚本的系统上。

如果满足这些条件,则:

echo=$(which echo)

会给您第一个echo进入的路径$PATH(在极端情况下除外),无论是否echo也恰好是一个内置的shell / alias / function。

在其他Shell中,您更喜欢:

  • zshecho==echoecho=$commands[echo]echo=${${:-echo}:c}
  • kshzshecho=$(whence -p echo)
  • yashecho=$(command -ev echo)
  • rcakanga:(echo=`whatis -p echo`提防带有空格的路径)
  • set echo (type -fp echo)

请注意,如果您只想运行echo命令,则不必获取其路径,您可以执行以下操作:

env echo this is not echoed by the builtin echo

例如,使用tcsh,以防止使用内置函数which

set Echo = "`env which echo`"

当您确实需要外部命令时

您可能想使用的另一种情况which是您实际上需要外部命令时。POSIX要求所有shell内置插件(如command)也可以作为外部命令使用,但是不幸的是,command在许多系统上并非如此。例如,很少command在基于Linux的操作系统上找到命令,而大多数操作系统都有which命令(尽管不同的操作系统具有不同的选项和行为)。

在不调用POSIX shell的情况下,执行命令的地方可能是您希望使用外部命令的情况。

system("some command line")popen()... C或各种语言的功能并调用一个shell来解析命令行,所以system("command -v my-cmd")在他们做的工作。例外是perl,如果它看不到任何外壳特殊字符(空格除外),则会优化外壳。这也适用于其反引号运算符:

$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0

$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs

:;上面添加的内容将强制perl在那里调用shell。通过使用which,您不必使用该技巧。


24
@Joe,whichcsh许多商业Unices 上的脚本。原因是历史原因,这就是为什么我给出了历史,所以人们了解了它的来历,为什么人们习惯了它,以及为什么实际上没有理由要使用它。是的,有些人使用(t)csh。不是每个人都使用Linux尚未
斯特凡Chazelas

12
阅读这篇文章后,我发现了很多答案的内容,但没有找到答案本身。它在这篇文章中实际在何处说明为什么使用which(而不是您可能要尝试使用的东西)which,历史which,其实现which,执行相关任务的其他命令或实际使用原因which?为什么其他命令更好?他们有什么不同which?他们如何避免它的陷阱?这个答案实际上在替代问题上比在问题上花了更多的单词which
user62251 2014年

1
与答案所声称的相反command -v,至少如果您通过不带路径的纯文件名参数调用它,则不检查执行权限。我通过破折号0.5.8和GNU bash 4.3.48进行了测试。
jarno

2
@StéphaneChazelas如果我通过创建新文件touch /usr/bin/mytestfile并随后运行command -v mytestfile,它将提供路径(而which mytestfile没有路径)。
jarno

2
@jarno,哦,是的,你是对的。bash如果找不到可执行文件,它将安装在一个非可执行文件上,因此它是“ OK”的(尽管实际上一个文件宁愿command -v/ type返回一个错误),因为这是它在运行时将尝试执行的命令mytestfile,但是dash行为是错误的,好像可执行文件cmd前面有一个不可执行文件,在执行时command -v返回该不可cmd执行文件将执行该可执行文件(错误的文件也会被散列)。FreeBSD sh(也基于ash)具有相同的错误。zsh,yash,ksh,mksh,bash和sh都可以。
斯特凡Chazelas

47

which已经解释了可能不希望使用的原因,但是这里有一些which实际失败的系统上的一些示例。

在类似Bourne的shell上,我们正在将的输出which与的输出进行比较typetype是内置的shell,这是基本事实,因为它是告诉我们如何调用命令的shell)。

许多情况是极端情况,但请记住which/ type经常在极端情况下使用(以查找意外行为的答案,例如:为什么该命令的行为如此,我叫哪个?)。

大多数系统,大多数类似于Bourne的shell:函数

最明显的情况是函数:

$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls

原因是,which仅报告有关的可执行文件,有时约别名(虽不总是那些的shell),而不是功能。

GNU的手册页上有一个残破的$@示例(因为他们忘了引用),该示例还介绍了如何使用它来报告功能,但是就像别名一样,因为它没有实现shell语法解析器,所以很容易上当:

$ which() { (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot "$@";}
$ f() { echo $'\n}\ng ()\n{ echo bar;\n}\n' >> ~/foo; }
$ type f
f is a function
f ()
{
echo '
}
g ()
{ echo bar;
}
' >> ~/foo
}
$ type g
bash: type: g: not found
$ which f
f ()
{
echo '
}
$ which g
g ()
{ echo bar;
}

大多数系统,大多数Bourne式外壳:内置

另一个明显的例子是建宏或关键字,which作为一个外部命令没有办法知道哪些内建你的shell有(和一些贝壳一样zshbash或者ksh可以动态加载内建):

$ type echo . time
echo is a shell builtin
. is a shell builtin
time is a shell keyword
$ which echo . time
/bin/echo
which: no . in (/bin:/usr/bin)
/usr/bin/time

(这并不适用于zsh地方which的内置)

Solaris 10,AIX 7.1,HP / UX 11i,Tru64 5.1和许多其他产品:

$ csh
% which ls
ls:   aliased to ls -F
% unalias ls
% which ls
ls:   aliased to ls -F
% ksh
$ which ls
ls:   aliased to ls -F
$ type ls
ls is a tracked alias for /usr/bin/ls

这是因为在大多数商业Unices中which(例如在3BSD的原始实现中)是一个csh读取脚本~/.cshrc。它将报告的别名是在那里定义的别名,无论您当前已定义的别名如何,也无论您实际使用的是哪个shell。

在HP / UX或Tru64中:

% echo 'setenv PATH /bin:/usr/bin' >> ~/.cshrc
% setenv PATH ~/bin:/bin:/usr/bin
% ln -s /bin/ls ~/bin/
% which ls
/bin/ls

(Solaris和AIX版本已通过$path在阅读之前进行保存~/.cshrc并在查找命令之前进行还原来解决此问题)

$ type 'a b'
a b is /home/stephane/bin/a b
$ which 'a b'
no a in /usr/sbin /usr/bin
no b in /usr/sbin /usr/bin

要么:

$ d="$HOME/my bin"
$ mkdir "$d"; PATH=$PATH:$d
$ ln -s /bin/ls "$d/myls"
$ type myls
myls is /home/stephane/my bin/myls
$ which myls
no myls in /usr/sbin /usr/bin /home/stephane/my bin

(当然,作为csh脚本,您不能指望它可以与包含空格的参数一起使用...)

CentOS 6.4,bash

$ type which
which is aliased to `alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
$ alias foo=': "|test|"'
$ which foo
alias foo=': "|test|"'
        /usr/bin/test
$ alias $'foo=\nalias bar='
$ unalias bar
-bash: unalias: bar: not found
$ which bar
alias bar='

在该系统上,系统范围内定义了一个别名,该别名包装了GNU which命令。

伪造的输出是因为which读的输出bashalias,但不知道如何正确地分析它,并使用启发式(每行一个别名,查找后找到的第一个命令|;&...)

CentOS上最糟糕的事情是它zsh拥有一个非常好的which内置命令,但是CentOS设法通过用一个对GNU不起作用的别名替换它来破坏了它which

Debian 7.0,ksh93:

(尽管适用于大多数具有许多外壳的系统)

$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which

在Debian上,/bin/which是一个/bin/sh脚本。在我的情况下,sh存在dash却是相同的bash

取消设置PATH不是禁用PATH查找,而是意味着使用系统的默认PATH(不幸的是,该默认路径在Debian上),没有人同意(dashbashhave /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binzshhas /bin:/usr/bin:/usr/ucb:/usr/local/binksh93has /bin:/usr/binmkshhas /usr/bin:/bin$(getconf PATH)),execvp()(像in一样env)has :/bin:/usr/bin(是,首先在当前目录中查找! ))。

这就是为什么which得到它上面的错误因为它使用dash的默认PATH这是从不同ksh93

使用GNU which报告:

which: no which in ((null))

(有趣的是/usr/local/bin/which,我的系统上确实存在一个实际上是随akanga脚本附带的脚本akangarc默认PATH为的shell导数/usr/ucb:/usr/bin:/bin:.))

bash,任何系统:

在一个克里斯指的是在他的回答

$ PATH=$HOME/bin:/bin
$ ls /dev/null
/dev/null
$ cp /bin/ls bin
$ type ls
ls is hashed (/bin/ls)
$ command -v ls
/bin/ls
$ which ls
/home/chazelas/bin/ls

同样在hash手动调用后:

$ type -a which
which is /usr/local/bin/which
which is /usr/bin/which
which is /bin/which
$ hash -p /bin/which which
$ which which
/usr/local/bin/which
$ type which
which is hashed (/bin/which)

现在which,有时甚至type失败的情况:

$ mkdir a b
$ echo '#!/bin/echo' > a/foo
$ echo '#!/' > b/foo
$ chmod +x a/foo b/foo
$ PATH=b:a:$PATH
$ which foo
b/foo
$ type foo
foo is b/foo

现在,有了一些shell:

$ foo
bash: ./b/foo: /: bad interpreter: Permission denied

和其他人:

$ foo
a/foo

无论是whichtype可以事先知道b/foo不能执行。一些炮弹一样bashksh或者yash,当调用foo确实会尝试运行b/foo并报告错误,而其他人(如zshashcshBournetcsh)将运行a/foo于失败execve()的系统调用b/foo


mksh实际使用的默认值有所不同$PATH:首先,使用操作系统的编译时常_PATH_DEFPATH量(最常见的是在BSD上),然后confstr(_CS_PATH, …)使用(POSIX),如果两者都不存在或失败,/bin:/usr/bin:/sbin:/usr/sbin则使用它们。
mirabilos 2014年

1
在第一个示例中,即使lsls从PATH 使用的函数。和which优良告诉你使用哪个/usr/bin/ls/usr/local/bin/ls。我看不到“为什么不使用哪个”
。...– rudimeier

@rudimeier,这which ls会给我/bin/ls无论是否ls函数调用/bin/ls/opt/gnu/bin/lsdir或什么都没有。IOW, which(即它的实现,IMMV)是给不相关的东西
斯特凡Chazelas

1
@StéphaneChazelas。不不不。我知道已经是我的ls一个功能。我知道我的ls函式是ls从呼叫PATH。现在which告诉我文件在哪里。您仅看到一个用例:“我的外壳将使用此命令做什么”。对于这种用例which是错误的,请更正。但是,在其他一些用例中,(GNU)which完全正确。
rudimeier

@rudimeter,取决于which实现。有些人会告诉您这是一个别名(如果您配置了别名,或者~/.cshrc您家中有一个别名),有些人会给您一条路径,但在某些情况下是错误的。sh -c 'command -v ls',尽管不够完美,但仍然更有可能为您提供不同要求的正确答案(也是标准要求)。
斯特凡Chazelas

21

(从我的快速浏览中),Stephane似乎没有提到的一件事是,which不知道您的Shell的路径哈希表。这样做的结果是,它可能会返回不代表实际运行结果的结果,这使其在调试中无效。


6

本着UNIX的精神:使每个程序做好一件事情。

如果目标是回答:哪个可执行文件具有名称?

Debian系统随附的哪个可执行程序是一个很好的答案。csh随附的包含别名,这是问题的根源。一些shell作为内部内置对象提供的目标有不同的目标。使用该可执行文件或使用此答案结尾处提供的脚本。

如果使用此脚本,则答案是干净,简单且有用的。

此目标将与您的问题的第一句话匹配:

在寻找可执行文件的路径时……

如果您的系统没有称为的可执行文件(大多数linux系统确实有一个可执行文件),则可以在PATH 中~/bin/which先创建一个/bin/可执行文件,这样个人可执行文件就可以覆盖系统文件,例如本文底部的一个:

该可执行文件将列出(默认情况下)在PATH中找到的所有可执行文件。如果仅需要第一个,则该选项-f可用。


在这一点上,我们陷入了另一个目标:

Shell将执行的操作(解析后)

那来自你的第二句话:

检查如果您在Unix shell中输入命令名会发生什么

第二个主题试图找到一个很好的答案,这个问题很难回答。壳具有不同的视图,极端情况和(至少)不同的解释。除此之外:

有很多不同的实用程序(类型,命令,时间,位置,位置,whatis,哈希等)。

当然,所有尝试都符合该目标。


避免哪个?

我们经常听到应该避免的情况。

我不知道:如果能which正常工作(至少在debian中),那为什么要这样说呢?

本着UNIX的精神:使每个程序做好一件事情。

外部程序which在做一两件事:查找具有相同的名称路径中第一个可执行的命令名称。并且做得相当好。

我不知道有任何其他程序或实用程序可以更基本地回答这个问题。因此,它很有用,可以在需要时使用。

最接近的替代方法似乎是:command -pv commandName,但这还将报告有关内置函数和别名的信息。答案不一样。

当然which,它是有限的,它不能回答所有问题,没有工具可以做到(嗯,还没有 ...)。但是,当用于回答它旨在回答的问题(上面的问题)时,它很有用。很像ed是受限制的,然后sed出现(或vi/ vim)。或者像是awk被限制并使得Perl出现并扩展。然而,edsed或/和awk,其中有具体的使用情况vim或者perl不是最好的工具。

为什么?

可能是因为which仅回答了shell用户可能会问的部分问题:

键入commandName时正在执行什么?


外在

(在许多系统中)哪个可以作为外部可执行文件使用。
调用该外部工具的唯一确定方法是使用env退出外壳,然后调用which(在所有外壳中都有效):

 $ env which which
 /usr/bin/which

或使用完整路径which(在不同系统上可能有所不同):

 /usr/bin/which which 

为什么hack需要那?因为某些壳(特别是zsh)隐藏which

 $ zsh -c 'which which'
 which: shell built-in command

作为外部工具(如env),可以完美地解释为什么它不报告外壳内部信息。像别名,函数,内置插件,特殊内置插件,shell(非导出)变量等:

 $ env which ls
 /usr/bin/ls
 $ env which ll       # empty output

ll(的通用别名ll='ls -l')的空输出表示ll与可执行程序无关,或者至少表明llPATH中没有命名的可执行文件。使用ll时应调用其他名称,在本例中为别名:

 $ type ll
 ll is aliased to `ls -l'

typecommand

命令typecommand -v是POSIX请求的。应该期望它们可以在大多数shell中工作,并且它们可以工作,除了csh,tcsh,fish和rc。

这两个命令可用于提供将执行命令的另一个观点。

whencewherewhereiswhatishash

那么,有whencewherewhereiswhatishash,和其他一些人。相似问题的所有不同答案。它们在不同的外壳中以不同的方式工作。可能whence是之后最常见的type。其他的是特殊的解决方案,它们以不同的方式回答相同的问题。

我们应该用什么代替呢?

也许which首先要知道如果由名称存在一个可执行的命令名,然后typecommand然后,如果命令名也尚未发现: whencewherewhereiswhatishash的顺序。


Shell脚本提供which可执行文件。

#! /bin/sh
set -ef; oldIFS=$IFS; IFS=:

say()( IFS=" "; printf "%s\n" "$*"; )
say "Simplified version of which."
usage(){ say Usage: "$0" [-f] args; }
if [ "$#" -eq 0 ]; then say Missing argument(s); usage; exit 2; fi

firstmatch=0
while getopts f whichopts; do
    case "$whichopts" in
        f) firstmatch=1 ;;
        ?) usage; exit 3 ;;
    esac
done
[ "$OPTIND" -gt 1 ] && shift `expr "$OPTIND" - 1`

allret=0; [ "$#" -eq 0 ] && allret=1
for program in "$@"; do
    ret=1
    for element in $PATH''; do
        case "$program" in
            */*) element="$program"; loop=0;;
            *)   element="${element:-.}/$program"; loop=1;;
        esac
        if [ -f "$element" ] && [ -x "$element" ]; then
            say "$element"
            ret=0
            if [ "$firstmatch" -eq 1 ] || [ "$loop" -eq 0 ]; then break; fi
        fi
    done
    [ "$ret" -eq 1 ] && allret=1
done

IFS="$oldIFS"
exit "$allret"

0

我们经常听到应该避免的情况。为什么?我们应该用什么代替呢?

我从未听说过。请提供具体示例。我会担心您的Linux发行版和已安装的软件包,因为这是which从哪里来的!

SLES 11.4 x86-64

在tcsh版本6.18.01中:

> which which

which: shell built-in command.

在bash 3.2-147版本中:

> which which

/usr/bin/which

> which -v

GNU which v2.19, Copyright (C) 1999 - 2008 Carlo Wood.
GNU which comes with ABSOLUTELY NO WARRANTY;
This program is free software; your freedom to use, change
and distribute this program is protected by the GPL.

whichutil-linux 的一部分, util-linux是Linux内核组织分发的标准软件包,用作Linux操作系统的一部分。它还提供了其他文件

/bin/dmesg
/bin/findmnt
/bin/logger
/bin/lsblk
/bin/more
/bin/mount
/bin/umount
/sbin/adjtimex
/sbin/agetty
/sbin/blkid
/sbin/blockdev
/sbin/cfdisk
/sbin/chcpu
/sbin/ctrlaltdel
/sbin/elvtune
/sbin/fdisk
/sbin/findfs
/sbin/fsck
/sbin/fsck.cramfs
/sbin/fsck.minix
/sbin/fsfreeze
/sbin/fstrim
/sbin/hwclock
/sbin/losetup
/sbin/mkfs
/sbin/mkfs.bfs
/sbin/mkfs.cramfs
/sbin/mkfs.minix
/sbin/mkswap
/sbin/nologin
/sbin/pivot_root
/sbin/raw
/sbin/sfdisk
/sbin/swaplabel
/sbin/swapoff
/sbin/swapon
/sbin/switch_root
/sbin/wipefs
/usr/bin/cal
/usr/bin/chrp-addnote
/usr/bin/chrt
/usr/bin/col
/usr/bin/colcrt
/usr/bin/colrm
/usr/bin/column
/usr/bin/cytune
/usr/bin/ddate
/usr/bin/fallocate
/usr/bin/flock
/usr/bin/getopt
/usr/bin/hexdump
/usr/bin/i386
/usr/bin/ionice
/usr/bin/ipcmk
/usr/bin/ipcrm
/usr/bin/ipcs
/usr/bin/isosize
/usr/bin/line
/usr/bin/linux32
/usr/bin/linux64
/usr/bin/look
/usr/bin/lscpu
/usr/bin/mcookie
/usr/bin/mesg
/usr/bin/mkzimage_cmdline
/usr/bin/namei
/usr/bin/rename
/usr/bin/renice
/usr/bin/rev
/usr/bin/script
/usr/bin/scriptreplay
/usr/bin/setarch
/usr/bin/setsid
/usr/bin/setterm
/usr/bin/tailf
/usr/bin/taskset
/usr/bin/time
/usr/bin/ul
/usr/bin/uname26
/usr/bin/unshare
/usr/bin/uuidgen
/usr/bin/wall
/usr/bin/whereis
/usr/bin/which
/usr/bin/write
/usr/bin/x86_64
/usr/sbin/addpart
/usr/sbin/delpart
/usr/sbin/fdformat
/usr/sbin/flushb
/usr/sbin/freeramdisk
/usr/sbin/klogconsole
/usr/sbin/ldattach
/usr/sbin/partx
/usr/sbin/rcraw
/usr/sbin/readprofile
/usr/sbin/rtcwake
/usr/sbin/setctsid
/usr/sbin/tunelp

我的util-linux是2.19版。发行说明可以轻松地追溯到v2.13(2007年8月28日)。不知道这是什么目的或目标,那件冗长的事被提出了331次,肯定没有得到回答。


2
请注意,这个问题没有提及它所指的Unix。Linux只是其中之一。
库萨兰达

2
如您所which -v显示的,那是GNU(在另一个答案中提到的最奢侈的GNU,并且绝不是Linux特有的),而不是AFAIK从未包含which实用程序的util-linux 。util-linux 2.19来自2011年,GNU 2.19来自2008
StéphaneChazelas
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.