是测试还是[或[[bash shell之间和其他shell之间更便携?

我知道我能做到

$ [ -w /home/durrantm ] && echo "writable"
writable

要么

$ test -w /home/durrantm && echo "writable"
writable

要么

$ [[ -w /home/durrantm ]] && echo "writable"
writable

I like using the third syntax. Are they equivalent in all ways and f要么 all negative and edge cases? Are there any differences in p要么tability, e.g. between bash on Ubuntu and on OS X 要么 older/newer bash versions, e.g. bef要么e/after 4.0 and do they both expand expressions the same way?

42
对于 [...] vs [[...]] vs test ...这个大多数重复的问题
额外 作者 Valters Vingolds,
有关测试文件可写性的特定问题,另请参阅如何非侵入性地测试对文件的写入权限?
额外 作者 G-Man,
有一种说法:“没有可移植的代码,只有已移植的代码”。我对此的建议:使用最易读的形式(可能[[...]])并在您想要支持的所有平台上尝试。模糊脚本没有多大用处,因此它们可以在您和您的目标受众都不会使用的古老平台上运行。它只会使您的代码难以阅读,引入不必要的错误甚至安全问题(就像它对openssl所做的那样)。
额外 作者 Sam Gamoran,

7 答案

是的,存在差异。最便携的是 test[] 。这些都是 POSIX test 规范的一部分</一>。

if ... fi 构造也是

由POSIX 并且应该是完全可移植的。

[[]] 是一个 ksh 功能,它也出现在 bash 的某些版本中(所有现代的) ,在 zsh 中,也许在其他人中,但不存在于 shdash 或其他各种更简单的shell中。

因此,要使脚本可移植,请使用 [] test if if ... fi 。

34
额外
[[的一个有趣特性是参数扩展不必引用: [[ - f $ file]] 事件,如果 $ file 包含空格字符。
额外 作者 Matt Enright,
@helpermethod也,内置正则表达式 [[$ a =〜^ reg。* exp。* $']]
额外 作者 GnP,
需要注意的是, bashzsh 已经支持 [[>很长一段时间了( bash 将它添加到了90年代后期, zsh 不迟于2000年,如果缺乏支持我会感到惊讶,所以如果没有,你不可能遇到任何一个版本[[</代码>。遇到不同的POSIX兼容shell(例如 dash )的可能性要大得多。
额外 作者 Luke Smith,

[ is synonym of the test command and it is simultaneously a bash builtin and separate command. But [[ is a bash keyword and works in some versions only. So for reasons of portability you are better off using single [] or test

[ -w "/home/durrantm" ] && echo "writable"
28
额外

Please note, that [] && cmd 是不一样的if .. fi construction.

Sometimes its behaviour its pretty similar and you can use [] && cmd instead of if .. fi. But only sometimes. If you have more then one command to execute if condition or you need if .. else .. fi be careful and whatch the logic.

几个例子:

[ -z "$VAR" ] && ls file || echo wiiii

是不一样的

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

因为如果 ls 失败, echo 将被执行,如果将不会发生这种情况。

另一个例子:

[ -z "$VAR" ] && ls file && echo wiii

是不一样的

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

虽然这种结构也会起作用

[ -z "$VAR" ] && { ls file ; echo wiii ; }

请注意; 后回声很重要,必须在那里。

所以我们可以说上面的恢复声明

[] && cmd == if first command is successful then execute the next one

if .. fi == if condition (which may be the test command as well) then execute command(s)

因此,对于 [[[仅使用 []之间的可移植性。

if is POSIX compatible. So if you have to choose between [ and if choose looking at your task and expected behaviour.

19
额外
我贬低了因为a)这主要是一个很好的答案,但它是对不同问题的答案。 b)你提出 [if 作为替补,但他们不是。实际上 && 正在替换 if[执行一些代码并返回一个状态,就像 lsgrep 一样。 if 根据if之后给出的命令(语句)的返回状态分支执行,它可以是任何命令(语句)。 && 只有在前一个语句返回0时才执行下一个语句,就像一个简单的 if..then..fi
额外 作者 GnP,
@rush你是对的,我错过了那里的编辑历史。我很抱歉。正如我所说,这主要是一个很好的答案,所以我纠正了我的投票。
额外 作者 GnP,
我可能只是在密集,但我没有看到差异会是什么......你能举个例子说这两个结构会给出不同的结果吗?
额外 作者 evilsoup,
@evilsoup,更新。可能我不是最好的解释者,但我希望现在能说清楚。
额外 作者 agrublev,
@gnp,好吧。 a)请检查迈克尔的评论。有一个简短的解释,最初有一个不同的问题。我刚刚给出了历史的答案。 b)答案主要是 && .. ||if ... else .. fi 之间的区别。
额外 作者 agrublev,
问题是关于[vs [[vs测试而不是关于if ... fi vs &&使它成为一个问题。不幸的是,这样做使得这个答案看起来不合时宜。抱怨没有得到问题最初集中在这导致了这一点。生活和(尝试)学习。 :)
额外 作者 ExpelledFromParadise,
非常好点,赶紧。我想你的第一个例子的安全形式是: [-z“$ VAR”] && {ls file;真正; } || echo wiiii 。它有点冗长,但它仍然比 if ... fi 构造更短。
额外 作者 DirkGently,

It's actually the && that is replacing the if, not the test: an if statement in shell scripting tests whether a command returned a "successful" (zero) exit status; in your example, the command is [.

因此,实际上有两件事你在这里变化:用于运行测试的命令,以及用于根据该测试的结果执行代码的语法。

测试命令:

  • test is a standardised command for evaluating properties of strings and files; in your example, you are running the command test -w /home/durrantm
  • [ is an alias of that command, equally standardised, which has a mandatory last argument of ] in order to look like a bracketed expression; don't be fooled, it's still just a command (you may even find that your system has a file called /bin/[)
  • [[ is an extended version of the test command built into some shells, but not part of the same POSIX standard; it includes extra options which you are not using here

条件表达式:

  • The && operator (standardised here) performs a logical AND operation, by evaluating two commands and returning 0 (which represents true) if they both return 0; it will only evaluate the second command if the first one returned zero, so it can be used as a simple conditional
  • The if ... then ... fi construct (standardised here) uses the same method of judging "truth", but allows for a compound list of statements in the then clause, rather than the single command afforded by an && short-circuit, and provides elif and else clauses, which are hard to write using only && and ||. Note that there are no brackets around the condition in an if statement.

因此,以下都是您的示例中同样可移植且完全等效的渲染:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

虽然以下也是等效的,但由于 [[

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
9
额外
是的我删除了if .... fi部分,使其成为1个问题。
额外 作者 ExpelledFromParadise,

如果你想在Bourne-like世界之外移植,那么:

test -w /home/durrantm && echo writable

是最便携的。它适用于Bourne, cshrc 系列的shell。

test -w /home/durrantm && echo "writable"

会在 rc 系列的shell中输出“writable”而不是 writablercesakanga ,其中并不特殊)。

[ -w /home/durrantm ] && echo writable

$ PATH 中没有 [命令的系统上的 cshrc 系列的shell中不起作用代码>(有些人已经知道 test 而不是 [别名)。

if [ -w /home/durrantm ]; then echo writabe; fi

仅适用于Bourne家族的贝壳。

[[ -w /home/durrantm ]] && echo writable

仅适用于 ksh (源自它), zshbash (Bourne系列中的所有3个)。

没有人可以在你需要的 fish shell中工作:

[ -w /home/durrantm ]; and echo writable

要么:

if [ -w /home/durrantm ]; echo writable; end
7
额外

为了便于携带,请使用 test / [。但是如果你不需要可移植性,为了你自己和其他人阅读你的脚本的理智,使用 [[。 :)

另请参阅 test,[和[href="http://mywiki.wooledge.org/BashFAQ/031"> BashFAQ 中的[[?]之间的区别。

7
额外

My most important reason for choosing either if foo; then bar; fi or foo && bar is whether the exit status of the whole command is important.

相比:

#!/bin/sh
set -e
foo && bar
do_baz

有:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

你可能会认为他们也这样做;但是如果 foo 失败(或者是假,取决于你的观点)那么在第一个例子中do_baz将不会被执行,因为脚本将退出... set -e 指示shell在任何命令返回false状态时立即退出。如果你做的事情非常有用:

cd /some/directory
rm -rf *

如果 cd 因任何原因失败,您不希望脚本继续运行。

0
额外
在任何一种情况下,没有失败的 foo 都不会中止脚本。这是 set -e 的特例(当命令被评估为条件时(在某些&&/||的左侧或在if/while/until/elsif ...条件下)。在两种情况下, bar 都会退出shell。
额外 作者 Stéphane Chazelas,
你想要 cd/some/directory && rm -rf - *cd/some/directory ||出口; rm -rf - * (仍然不删除隐藏文件)。我个人不喜欢使用 set -e 作为不努力编写正确代码的借口。
额外 作者 Stéphane Chazelas,