Sté*_*las 422 shell history which portability
当寻找的路径,可执行文件或检查,如果你在Unix shell中输入命令的名称会发生什么,有不同的公用事业过多(which,type,command,whence,where,whereis,whatis,hash,等)。
我们经常听说which应该避免。为什么?我们应该用什么来代替?
Sté*_*las 463
以下是您从未想过不想知道的所有内容:
在类似 Bourne 的 shell 脚本中获取可执行文件的路径名(有一些注意事项;见下文):
ls=$(command -v ls)
Run Code Online (Sandbox Code Playgroud)
要找出给定的命令是否存在:
if command -v given-command > /dev/null 2>&1; then
echo given-command is available
else
echo given-command is not available
fi
Run Code Online (Sandbox Code Playgroud)
在类似 Bourne 的交互式 shell 的提示下:
type ls
Run Code Online (Sandbox Code Playgroud)
该which命令是 C-Shell 的一个破碎的遗产,最好单独留在类似 Bourne 的 shell 中。
作为脚本的一部分或在 shell 提示符下以交互方式查找该信息是有区别的。
在 shell 提示符下,典型的用例是:这个命令的行为很奇怪,我使用的是正确的吗?我打字时到底发生了什么mycmd?我可以进一步看看它是什么吗?
在这种情况下,您想知道在调用命令而不实际调用命令时 shell 做了什么。
在 shell 脚本中,它往往是完全不同的。在 shell 脚本中,如果您只想运行命令,那么您没有理由想知道命令在哪里或命令是什么。通常,您想知道的是可执行文件的路径,因此您可以从中获取更多信息(例如与该文件相关的另一个文件的路径,或者从该路径处的可执行文件的内容中读取信息)。
交互,你可能想知道所有的my-cmd系统上可用的命令,在脚本中,很少如此。
大多数可用工具(通常是这种情况)都设计为可交互使用。
先说一点历史。
直到 70 年代后期的早期 Unix shell 都没有函数或别名。只有传统的在$PATH. csh1978年左右推出的别名(虽然csh最初发布在2BSD,在1979年5月),并且还的加工.cshrc,为用户定制的外壳(每壳,如csh,读.cshrc脚本,即使没有互动等)。
虽然 Bourne shell 于 1979 年早些时候在 Unix V7 中首次发布,但功能支持是在很晚之后才添加的(1984 年在 SVR2 中),无论如何,它从来没有一些rc文件(.profile用于配置您的环境,而不是 shell本身)。
csh 比 Bourne shell 更受欢迎(尽管它的语法比 Bourne shell 糟糕得多)它为交互式使用添加了许多更方便和更好的功能。
在3BSD(1980) 中,为用户添加了一个whichcsh 脚本csh以帮助识别可执行文件,这与您which在当今许多商业 Unices(如 Solaris、HP/UX、AIX 或 Tru64)上可以找到的脚本几乎没有什么不同。
该脚本读取用户的~/.cshrc(就像所有csh脚本一样,除非使用 调用csh -f),并在别名列表和$path(csh基于 维护的数组)中查找提供的命令名称$PATH。
你去吧:which首先出现在当时最流行的 shell(csh直到 90 年代中期仍然流行),这是它被记录在书中并仍然被广泛使用的主要原因。
请注意,即使对于csh用户,该whichcsh 脚本也不一定为您提供正确的信息。它得到中定义的别名,~/.cshrc通过你可能已经在提示符下或例如后面定义,而不是那些source荷兰国际集团的另一个csh文件,(虽然这不会是一个好主意),PATH可能会在被重新定义~/.cshrc。
运行which从一个Bourne shell命令将在您的定义仍然查找别名~/.cshrc,但如果你没有一个,因为你不使用csh,这将仍然可能得到你正确的答案。
直到 1984 年在 SVR2 中使用type内置命令才将类似的功能添加到 Bourne shell 。它是内置的(与外部脚本相反)这一事实意味着它可以为您提供正确的信息(在某种程度上),因为它可以访问 shell 的内部结构。
初始type命令遇到与which脚本类似的问题,因为如果找不到命令,它不会返回失败退出状态。此外,对于可执行文件,相反which,它输出类似ls is /bin/ls,而不是仅仅/bin/ls这使得它不太容易使用的脚本。
Unix 版本 8(未正式发布)的 Bourne shell 将其type内置程序重命名为whatis并扩展为还报告有关参数和打印函数定义的信息。它还type解决了在找不到名称时不返回失败的问题。
rc,Plan9(曾经是 Unix 的继任者)(及其衍生物,如akanga和es)的外壳whatis也有。
Korn shell(POSIXsh定义所基于的一个子集)开发于 80 年代中期,但在 1988 年之前并未广泛使用csh,在 Bourne shell 之上添加了许多功能(行编辑器、别名...)。它添加了自己的whence内置(除了type),它采用了多个选项(-v提供type类似 - 的详细输出,并-p仅查找可执行文件(而不是别名/函数...))。
与 AT&T 和 Berkeley 之间版权问题的混乱相吻合,80 年代末 90 年代初出现了一些免费的软件外壳实现。所有的 Almquist shell(ash用于替代 BSD 中的 Bourne shell),ksh(pdksh)的公共领域实现,bash(由 FSF 赞助),zsh在 1989 年和 1991 年之间问世。
Ash,虽然打算替代 Bourne shell,但type直到很久以后(在 NetBSD 1.3 和 FreeBSD 2.3 中)才具有内置,尽管它有hash -v. OSF/1/bin/sh有一个type内置函数,它在 OSF/1 v3.x 之前总是返回 0。bash没有添加 awhence但添加了一个-p选项来type打印路径(type -p就像whence -p)并-a报告所有匹配的命令。tcsh由which内置并增加了一个where表现得象命令bash的type -a。zsh都有。
该fish外壳(2005)具有type作为一个功能来实现命令。
在which从NetBSD的取出CSH脚本同时(因为它是在tcsh中和其他炮弹没有多大用处的内置),并加入到功能whereis(如时调用which,whereis行为就像which但它仅查找可执行程序$PATH)。在 OpenBSD 和 FreeBSD 中,which也改为用 C 编写的,$PATH只在其中查找命令。
which在各种具有不同语法和行为的 Unice 上有许多命令的实现。
在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
Run Code Online (Sandbox Code Playgroud)
zsh,在zsh/parameters模块中也将命令哈希表作为commands关联数组:
$ print -r -- $commands[ls]
/bin/ls
Run Code Online (Sandbox Code Playgroud)
该whatis实用程序(Unix V8 Bourne shell 或 Plan 9 rc/ 中的实用程序除外es)实际上并不相关,因为它仅用于文档(greps 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)
Run Code Online (Sandbox Code Playgroud)
将是做到这一点的标准方法。
不过有几个问题:
type, which, command -v... 都使用启发式方法来找出路径。它们遍历$PATH组件并找到您对其具有执行权限的第一个非目录文件。但是,根据 shell,在执行命令时,它们中的许多(Bourne、AT&T ksh、zsh、ash...)只会按照 的顺序执行它们,$PATH直到execve系统调用不返回错误. 例如,如果$PATHcontains/foo:/bar并且您想执行ls,它们将首先尝试执行,否则会/foo/ls失败/bar/ls。现在执行/foo/ls可能会失败,因为您没有执行权限,但还有许多其他原因,例如它不是有效的可执行文件。command -v ls会报告/foo/ls您是否拥有 的执行权限/foo/ls,但如果不是有效的可执行文件,运行ls可能实际上会运行。/bar/ls/foo/lsfoo是内置函数或别名,则command -v foo返回foo. 对于诸如ash, pdkshor 之类的外壳,如果包含空字符串并且当前目录中有一个可执行文件zsh,它也可能返回。在某些情况下,您可能需要考虑到这一点。例如,请记住,内置函数列表随 shell 实现而变化(例如,有时内置于 busybox ),例如可以从环境中获取函数。foo$PATHfoomountshbash$PATH包含相对路径组件(通常.或空字符串,它们都引用当前目录但可以是任何内容),取决于外壳,command -v cmd可能不会输出绝对路径。因此,您在运行时获得的路径在您到达其他地方command -v后将不再有效cd。/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
Run Code Online (Sandbox Code Playgroud)
which(t)csh在csh和 中tcsh,您没有太多选择。在 中tcsh,这很好,就像which内置的一样。在 中csh,这将是系统which命令,在某些情况下可能无法执行您想要的操作。
使用可能有意义的一种情况which是,如果您想知道命令的路径,而忽略bash、csh(不是tcsh)dash、 或Bourneshell 脚本中的潜在 shell 内置函数或函数,即没有的 shell whence -p(如ksh或zsh) , command -ev(like yash), whatis -p( rc, akanga) 或在可用且不是脚本的系统上的内置which(like tcshor zsh)。whichcsh
如果满足这些条件,则:
echo=$(which echo)
Run Code Online (Sandbox Code Playgroud)
会给你的第一个路径echo中$PATH(除了在角落的情况下),无论是否echo也恰好是一个shell内建/别名/功能与否。
在其他 shell 中,您更喜欢:
echo==echo或echo=$commands[echo]或echo=${${:-echo}:c}echo=$(whence -p echo)echo=$(command -ev echo)echo=`whatis -p echo`注意带空格的路径)set echo (type -fp echo)请注意,如果您只想运行该echo命令,则不必获取其路径,只需执行以下操作:
env echo this is not echoed by the builtin echo
Run Code Online (Sandbox Code Playgroud)
例如,使用tcsh, 以防止使用内置函数which:
set Echo = "`env which echo`"
Run Code Online (Sandbox Code Playgroud)
您可能想要使用的另一种情况which是您确实需要外部命令。POSIX 要求所有 shell 内置命令(如command)也可用作外部命令,但不幸的是,command在许多系统上并非如此。例如,很少command在基于 Linux 的操作系统上找到命令,而大多数操作系统都有which命令(尽管不同的命令具有不同的选项和行为)。
您可能需要外部命令的情况将是您在不调用 POSIX shell 的情况下执行命令的任何地方。
C 或各种语言的system("some command line"), popen()... 函数确实会调用 shell 来解析该命令行,因此system("command -v my-cmd")请使用它们。一个例外是perl如果没有看到任何 shell 特殊字符(空格除外),它会优化 shell。这也适用于它的反引号运算符:
$ 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
Run Code Online (Sandbox Code Playgroud)
添加的那:;上面的力量perl来调用一个shell那里。通过使用which,您将不必使用该技巧。
Sté*_*las 62
可能不想使用的原因which已经解释过,但这里有几个which实际失败的系统的示例。
在类似 Bourne 的 shell 上,我们将 的输出which与type(type作为一个 shell 内置函数,它意味着是基本事实,因为它是 shell 告诉我们它将如何调用命令) 的输出。
许多情况都是极端情况,但请记住,which/type经常在极端情况下使用(以找到意外行为的答案,例如:为什么该命令的行为如此,我要调用哪个?)。
最明显的例子是函数:
$ type ls
ls is a function
ls ()
{
[ -t 1 ] && set -- -F "$@";
command ls "$@"
}
$ which ls
/bin/ls
Run Code Online (Sandbox Code Playgroud)
原因是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;
}
Run Code Online (Sandbox Code Playgroud)
另一个明显的情况是内置函数或关键字,因为which作为外部命令无法知道您的 shell 具有哪些内置函数(有些 shell 像zsh,bash或者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
Run Code Online (Sandbox Code Playgroud)
(这并不适用于zsh地方which是内置)
$ 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
Run Code Online (Sandbox Code Playgroud)
这是因为在大多数商业 Unices 上which(就像在 3BSD 上的原始实现中一样)是一个csh读取~/.cshrc. 它将报告的别名是在那里定义的别名,无论您当前定义的别名如何,也无论您实际使用的外壳如何。
在 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
Run Code Online (Sandbox Code Playgroud)
(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
Run Code Online (Sandbox Code Playgroud)
或者:
$ 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
Run Code Online (Sandbox Code Playgroud)
(当然,作为一个csh脚本,你不能指望它与包含空格的参数一起工作......)
$ 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='
Run Code Online (Sandbox Code Playgroud)
在该系统上,有一个在系统范围内定义的别名,用于包装 GNUwhich命令。
虚假输出是因为which读取bash's的输出alias但不知道如何正确解析它并使用启发式(每行一个别名,在 a |, ;, &...之后查找第一个找到的命令)
CentOS 上最糟糕的事情是它zsh有一个完美的which内置命令,但 CentOS 设法通过用一个非工作别名 GNU 替换它来破坏它which。
(虽然适用于大多数具有许多外壳的系统)
$ unset PATH
$ which which
/usr/local/bin/which
$ type which
which is a tracked alias for /bin/which
Run Code Online (Sandbox Code Playgroud)
在 Debian 上,/bin/which是一个/bin/sh脚本。在我的情况下,sh是dash但是当它是bash.
取消设置PATH不是禁用PATH查找,而是意味着使用系统的默认 PATH不幸的是在 Debian 上,没有人同意 ( dashand bashhave /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, zshhas /bin:/usr/bin:/usr/ucb:/usr/local/bin, ksh93has /bin:/usr/bin, mkshhas /usr/bin:/bin( $(getconf PATH)), execvp()(就像 in env) has :/bin:/usr/bin(是的,首先在当前目录中查找! ))。
这就是为什么which上面出错的原因,因为它使用dash的默认值PATH与ksh93's不同
GNUwhich并没有更好的报告:
which: no which in ((null))
Run Code Online (Sandbox Code Playgroud)
(有趣的是/usr/local/bin/which,我的系统上确实有一个akanga脚本,它实际上是一个附带的脚本akanga(rc默认PATH为的shell 衍生物/usr/ucb:/usr/bin:/bin:.))
克里斯在他的回答中提到的一个:
$ 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
Run Code Online (Sandbox Code Playgroud)
同样在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)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
现在,有一些贝壳:
$ foo
bash: ./b/foo: /: bad interpreter: Permission denied
Run Code Online (Sandbox Code Playgroud)
和其他人:
$ foo
a/foo
Run Code Online (Sandbox Code Playgroud)
既which不能也type不能提前知道b/foo不能执行。某些外壳程序(如bash、ksh或yash)在调用foo时确实会尝试运行b/foo并报告错误,而其他外壳程序(如zsh、ash、csh、Bourne、tcsh)将a/foo在execve()系统调用 on失败时运行b/foo。
Chr*_*own 25
一件事(从我的快速浏览中)似乎 Stephane 没有提到的一件事是which不知道您的 shell 的路径哈希表。这会导致它可能返回不代表实际运行内容的结果,从而使其在调试中无效。
我which经常使用这样的方式快速进入可执行文件:vim $(which my_executable). 最近我发现这个帖子,很惊讶——我一直都做错了?但后来我检查了所有答案,并没有找到任何which完全放弃的理由。此外,没有一个答案为我提供了以下内容的直接替代vim $(which my_executable):(输出保持原样)
$ type -a pacman
pacman is aliased to `pacman'
pacman is /usr/bin/pacman
$ command -v pacman
alias pacman='pacman'
$ which pacman
/usr/bin/pacman
$ bash --version
GNU bash, version 5.1.8(1)-release (x86_64-pc-linux-gnu)
Run Code Online (Sandbox Code Playgroud)
which 命令是 C-Shell 的一个被破坏的遗产,最好单独保留在类似 Bourne 的 shell 中。
我认为你应该使用任何工具,只要它能满足你的所有需求。我用来which在 中查找可执行路径$PATH,当然它不是一个合适的工具来调查命令背后的实际内容,然后type和command会更适合。
| 归档时间: |
|
| 查看次数: |
66636 次 |
| 最近记录: |