test或[或[[在bash外壳之间和其他外壳之间都更容易移植吗?


44

我知道我能做

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

要么

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

要么

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

我喜欢使用第三种语法。它们在所有方面以及所有消极和极端情况下是否等效?在可移植性方面是否存在任何差异,例如在Ubuntu和OS X上的bash或较旧/较新的bash版本(例如4.0之前/之后)之间,它们是否都以相同的方式扩展表达式?


2
对于[ … ]vs [[ … ]]vs test …,在这个几乎重复的问题中,有更完整的答案。
吉尔(Gilles)'所以

有关测试文件可写性的特定问题,另请参阅如何非侵入性地测试对文件的写访问权限?
G-Man说'Resstate Monica'2014/

1
有一句话:“没有可移植的代码,只有已经移植的代码”。关于此问题,我的建议是:使用可读性最高的格式(可能是[[...]]),并在要支持的所有平台上尝试使用。掩盖您的脚本没有太大用处,因此它们可以在您和您的目标受众都没有使用的古老平台上运行。这只会使您的代码难以阅读,引入不必要的错误,甚至可能会带来安全问题(就像openssl所做的那样)。
stefan.schwetschke 2014年

Answers:


31

[test命令的同义词,同时是bash内置命令和单独命令。但是[[bash关键字,仅在某些版本中有效。因此,出于可移植性的考虑,最好[]还是使用single 或test

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

2
[是POSIX内置还是Bash内置?
桑德堡

@Sandburg POSIX是一个标准,因此不能内置。它没有定义是否应该将某些内容内置到解释器中,而只是定义它必须做什么。这些规则适用于两种形式,test等等[。如果外壳程序可以自行执行评估,则可以节省您的过程并使其更快一些,但结果必须相同。
巴绍

@Bachsau仍然无法回答Sandburg的问题,即posix的行为是否[由POSIX定义,因此具有最大的可移植性(如果我没有记错的话,这似乎是这个问题的全部重点)
JamesTheAwesomeDude

@Sandburg(和其他任何人碰到这个绊脚石):是的,它看起来既像[test 是POSIX我似乎没有发现“推荐”的区别,尽管我能发现的唯一区别是test“不能识别“-”参数[作为表示选项结束的定界符]“(?-表示“ “” 认为是的参数结尾[,无论如何我实际上都无法提出一个好的测试用例。但是我离题了。您将使用它。IMO ,[看起来很优雅,但test显然是一个命令)
JamesTheAwesomeDude

35

是的,有差异。最便携的是test[ ]。这些都是POSIX test规范的一部分。

if ... fi构造也由POSIX定义,并且应该是完全可移植的。

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

因此,为了使你的脚本便于携带,使用[ ]testif ... fi


10
只是要注意,bashzsh已支持[[了很长一段时间(bash在90年代后期,添加它zsh不迟于2000,如果我会感到惊讶以往缺乏支持),所以你不可能遇到一个版本的或者没有[[。遇到不同的POSIX兼容外壳程序(例如dash)的可能性更大。
chepner 2014年

1
的一个有趣功能[[是不必引号参数扩展:[[-f $file]]如果$file包含空格字符,则事件有效。
helpermethod 2014年

2
@helpermethod还内置了正则表达式[[ $a =~ ^reg.*exp.*$' ]]
GnP 2014年

20

请注意,这[] && cmdif .. fi构造不同。

有时它的行为非常相似,您可以使用[] && cmd代替if .. fi。但只有有时。如果您有多个命令,则在有条件的情况下要执行一条命令,或者您需要if .. else .. fi小心并取消逻辑。

几个例子:

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

与...不同

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

因为如果ls失败,echo将被执行,而不会发生if

另一个例子:

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

与...不同

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

虽然这种结构会起到相同的作用

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

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

所以上面的恢复语句我们可以说

[] && cmd ==如果第一个命令成功,则执行下一个

if .. fi ==如果条件(也可能是测试命令),则执行命令

因此,对于便携性[[[使用的[唯一。

if与POSIX兼容。因此,如果您必须在两者之间进行选择,[然后if选择查看您的任务和预期的行为。


我可能只是很稠密,但我看不出会有什么区别……您能举一个例子,说明这两种构造会产生不同的结果吗?
evilsoup 2014年

1
@evilsoup,已更新。也许我不是最好的解释者,尽管我希望现在会清楚。
2014年

1
非常好点,着急。我认为您的第一个示例的安全格式为:[ -z "$VAR" ] && { ls file; true; } || echo wiiii。它有点冗长,但仍比if...fi结构短。
下午14年

提出的问题是关于[vs [[vs vs test,而不是是否... fi vs &&使其成为一个问题。不幸的是,这样做会使这个答案显得不合时宜。道歉最初未能引起人们的关注,这导致了这一问题。生活和(尝试)学习。:)
Michael Durrant 2014年

我拒绝投票是因为a)这主要是一个很好的答案,但这是对其他问题的答案。b)您提出[if作为替代,但不是。实际上&&是替换if[执行一些代码并返回状态,就像ls并且grep会那样。if根据if之后给出的命令(语句)的返回状态分支执行,它可以是任何命令(语句)。&&仅当前一个返回0时才执行下一条语句,就像simple一样if..then..fi
GnP

9

实际上&&是用代替if,而不是testifshell脚本中的一条语句测试命令是否返回“成功”(零)退出状态。在您的示例中,命令为[

因此,实际上您在这里有两点不同:用于运行测试的命令,以及用于根据测试结果执行代码的语法。

测试命令:

  • test是用于评估字符串和文件属性的标准命令;在您的示例中,您正在运行命令test -w /home/durrantm
  • [是该命令的别名(同样标准化),该命令的最后一个自变量为],以使其看起来像方括号表达式;不要上当,它仍然只是一个命令(您甚至可能发现您的系统中有一个名为的文件/bin/[
  • [[是内置在某些shell中但不是同一POSIX标准一部分的test命令的扩展版本;它包括您在此处未使用的其他选项

条件表达式:

  • &&操作者(这里标准化)进行逻辑与运算,通过评估两个命令并返回0(表示真),如果他们都返回0; 仅当第一个命令返回零时,才会评估第二个命令,因此可以将其用作简单的条件
  • if ... then ... fi构建体(此处标准化)使用判定“真”的相同的方法,但允许语句的在化合物列表then条款,而不是由一个提供的单个命令&&的短路,并提供elifelse子句是很难写仅使用&&||请注意,if语句中的条件没有括号

因此,以下示例均具有相同的可移植性,并且完全等效:

  • 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

是的,我删除了if .... fi部分,使之成为1个问题。
迈克尔·杜兰特

7

要获得可移植性,请使用test/ [。但是,如果您不需要可移植性,那么出于您自己和其他阅读脚本使用情况的人的理智起见[[。:)

也看到What is the difference between test, [ and [[ ?BashFAQ


7

如果您希望在类似Bourne的世界之外具有可移植性,那么:

test -w /home/durrantm && echo writable

是最便携的。它适用于伯恩csh及其rc家人的贝壳。

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

将输出"writable",而不是writable在贝壳rc系列(rcesakanga,其中"是不是特殊的)。

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

在没有命令的系统上(或已知但没有别名)在cshrc系列的外壳中无法使用。[$PATHtest[

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

0

选择一个if foo; then bar; fi或一个的最重要原因foo && bar是整个命令的退出状态是否重要。

相比:

#!/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立即退出。如果您正在执行以下操作,则非常有用:

cd /some/directory
rm -rf *

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


1
foo无论哪种情况,失败都不会中止脚本。这是一种特殊情况set -e(当命令被评估为条件(在某些&& / ||或if / while / until / elsif ...条件的左侧)时bar,两种情况均将导致失败。
StéphaneChazelas 2014年

2
您想要cd /some/directory && rm -rf -- *还是cd /some/directory || exit; rm -rf -- *(仍然不会删除隐藏文件)。我个人不喜欢set -e以借口不努力编写正确代码的想法。
斯特凡Chazelas
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.