这是您从未想过的所有信息:
摘要
要在类似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年代后期都没有功能或别名。仅以传统方式查找可执行文件$PATH
。csh
1978年左右推出的别名(虽然csh
最初发布在2BSD
,在1979年5月),并且还的加工.cshrc
,为用户定制的外壳(每一个外壳,如csh
读取.cshrc
,即使不是交互式像脚本)。
Bourne shell于1979年初在Unix V7中首次发布,而功能支持只是在后来才添加(SVR2中为1984),无论如何,它从来没有任何rc
文件(.profile
用于配置环境,而不是shell 本身)。
csh
它比Bourne Shell流行得多(因为它的语法比Bourne Shell糟糕得多),它为交互使用添加了许多更方便,更漂亮的功能。
在3BSD
(1980)中,为用户添加了一个which
csh脚本csh
以帮助识别可执行文件,并且与which
当今许多商业Unices(例如Solaris,HP / UX,AIX或Tru64)一样,您可以找到几乎与众不同的脚本。
该脚本读取用户的脚本~/.cshrc
(就像所有csh
脚本一样,除非使用调用csh -f
),然后在别名列表和$path
(csh
基于的数组)中查找提供的命令名称$PATH
。
在这里,您which
发现它是当时最流行的外壳程序的第一名(csh
直到90年代中期仍很流行),这是它被记录在书中并仍被广泛使用的主要原因。
请注意,即使对于csh
用户,该which
csh脚本也不一定能为您提供正确的信息。它得到的别名是在中定义的~/.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
(及其派生类如akanga
and es
)whatis
也是如此。
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 -p
如whence -p
)并-a
报告所有匹配的命令。tcsh
由which
内置并增加了一个where
表现得象命令bash
的type -a
。zsh
全部拥有。
该fish
外壳(2005)具有type
作为一个功能来实现命令。
which
同时,csh脚本已从NetBSD中删除(因为它内置于tcsh中,并且在其他shell中使用不多),并且所添加的功能whereis
(被调用为时which
,whereis
其行为类似,which
只是它仅在中查找可执行文件$PATH
)。在OpenBSD和FreeBSD中,which
也被更改为用C编写的仅在其中查找命令的代码$PATH
。
实作
在which
具有不同语法和行为的各种Unices上有数十种命令实现。
在Linux上(tcsh
和中的内置功能除外zsh
),我们发现了几种实现。例如,在最近的Debian系统上,它是一个简单的POSIX shell脚本,用于在中查找命令$PATH
。
busybox
也有一个which
命令。
有一种GNU
which
可能是最奢侈的一种。它尝试将which
csh脚本的功能扩展到其他shell:您可以告诉它别名和函数是什么,以便它可以为您提供更好的答案(我相信某些Linux发行版为此设置了一些全局别名bash
) 。
zsh
有几个运算符可扩展到可执行文件的路径:=
文件名扩展运算符和:c
历史记录扩展修饰符(此处应用于参数扩展):
$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls
zsh
在zsh/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
命令(无选项)。这是所有(where
,which
,whence
不以任何标准中规定)
最新版本,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
是内置的)。
在以外的炮弹tcsh
和zsh
,which
只要有任何我们没有别名或函数由同名可能会告诉你给定的可执行文件的路径~/.cshrc
,~/.bashrc
或任何shell启动文件,你不定义$PATH
你的~/.cshrc
。如果为它定义了别名或函数,则它可能不会告诉您有关别名或告诉您的错误信息。
如果您想通过给定名称了解所有命令,则没有任何可移植的内容。你会使用where
的tcsh
还是zsh
,type -a
在bash
或者zsh
,whence -a
在ksh93的和其他炮弹,你可以用type
结合which -a
这可能工作。
推荐建议
获取可执行文件的路径
现在,要获取脚本中可执行文件的路径名,请注意以下几点:
ls=$(command -v ls)
将是这样做的标准方法。
但是有一些问题:
- 不执行可执行文件就无法知道其路径。所有
type
,which
,command -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
。一些炮弹一样ash
,pdksh
或者zsh
,它也可能返回foo
如果$PATH
包括空字符串,并且有一个可执行文件foo
在当前目录下的文件。在某些情况下,您可能需要考虑到这一点。请记住,例如,内建的列表随shell实现的不同而有所变化(例如,mount
有时内置于busybox sh
),并且例如bash
可以从环境中获取功能。
- 如果
$PATH
包含相对路径组件(通常.
是空字符串,都引用当前目录,但可以是任何东西),则取决于外壳程序,command -v cmd
可能不会输出绝对路径。因此,在其他地方运行command -v
之后,您在运行时获得的路径将不再有效cd
。
- 传闻:与ksh93的外壳,如果
/opt/ast/bin
(尽管确切的路径可以在不同的系统,我相信会发生变化)是你$PATH
,ksh93的将提供一些额外的内置函数(chmod
,cmp
,cat
...),但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
是,如果您想知道命令的路径,而忽略bash
,csh
(不是tcsh
)dash
,或Bourne
shell脚本中潜在的shell内建函数或函数,即没有shell的shell whence -p
(如like ksh
或zsh
) ,command -ev
(如yash
),whatis -p
(rc
,akanga
)或内置脚本which
(如tcsh
或zsh
)在which
可用且不是csh
脚本的系统上。
如果满足这些条件,则:
echo=$(which echo)
会给您第一个echo
进入的路径$PATH
(在极端情况下除外),无论是否echo
也恰好是一个内置的shell / alias / function。
在其他Shell中,您更喜欢:
- zsh:
echo==echo
或echo=$commands[echo]
或echo=${${:-echo}:c}
- ksh,zsh:
echo=$(whence -p echo)
- yash:
echo=$(command -ev echo)
- rc,akanga:(
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
,您不必使用该技巧。
which
都假设是交互式外壳上下文。这个问题被标记为/可移植性。因此,在这种情况下,我将问题解释为“使用什么,而不是在which
中找到给定名称的第一个可执行文件$PATH
”。大多数答案和反对理由都which
涉及别名,内建函数和函数,这些别名在大多数现实世界中的可移植外壳脚本中都具有学术意义。运行shell脚本时,不会继承本地定义的别名(除非您使用来提供别名.
)。