我正在读为 Bourne shell 编写的 Maven 包装器的源代码。我遇到过这些行:
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
# snip
expr
arg1
当与and一起使用时,arg2
a与正则:
表达式匹配。通常,结果是匹配字符的数量,例如:arg1
arg2
$ expr foobar : foo
3
但是,当使用捕获括号 (\(
和\)
) 时,它返回第一个捕获括号的内容:
$ expr foobar : '\(foo\)'
foo
到目前为止,一切都很好。
如果我在我的机器上评估上面引用的源中的表达式,我会得到:
$ javaExecutable=$(which javac)
$ expr "$javaExecutable" : '\([^ ]*\)'
/usr/bin/javac
对于不存在的可执行文件:
$ nonExistingExecutable=$(which sjdkfjkdsjfs)
$ expr "$nonExistingExecutable" : '\([^ ]*\)'
这意味着对于不存在的可执行文件,输出是带有换行符的空字符串。
which javac
在源代码中让我困惑的是( arg1
to )的输出如何expr
返回字符串no
?
是否有某个版本在找不到可执行文件时不which
返回任何内容,而是返回?no
如果不是,这个语句总是评估为真,那就很奇怪了。
答案1
传统上,which
是一个 csh 脚本,它打印一条错误消息no foo in /usr/bin:/bin
,并返回一个成功状态。 (至少有一个常见版本,可能还有其他行为不同的版本。)示例从 FreeBSD 1.0 开始(是的,那是古老的):
if ( ! $?found ) then
echo no $arg in $path
endif
(这个经典的实现也因加载用户的 而臭名昭著.cshrc
,这可能会更改PATH
,从而导致输出错误。)
现代系统通常有不同的 实现which
,用 C 或 sh 编写,并遵循处理错误条件的现代标准:没有输出到 stdout 和非零退出状态。
答案2
从技术上讲,这是检查是否第一个字(或者更确切地说,第一个非空格字符序列)在输出中。并且有些which
实现并没有保持沉默:
zsh
写入which foo
标准foo not found
输出- GNU
which foo
写入which: no foo in (<contents of $PATH>)
stderr - 我没有这些可以测试,但是一堆旧 Unix 的联机帮助页上有这样的措辞:“如果在路径中找不到具有参数名称的可执行文件,则打印诊断信息”(尽管我希望诊断信息能够打印到 stderr)。
我不知道以 开头的打印输出是哪个特定变体no
,但 GNUwhich
可能受到它的启发或者成为它的灵感。