为什么shell脚本比较常使用x$VAR = xyes?

87
我往往在使用自动工具(autoconf、automake)的项目构建脚本中看到这种情况。当有人想要检查一个 shell 变量的值时,他们经常使用这个习语:
if test "x$SHELL_VAR" = "xyes"; then
...

这种方法相对于仅检查值有什么优点?
if test $SHELL_VAR = "yes"; then
...

我想这种情况肯定有原因,但我无法想出是什么原因。


2
请参阅在“x$Variable”中x的shell脚本目的。该问题是此问题的副本。 - Jonathan Leffler
2
请参阅 使用 X"" 在 Bash 中测试空字符串。该问题实际上也是此问题的重复,或涵盖了大部分相同的内容。这两个重复问题都有一些很好的答案。 - Jonathan Leffler
7个回答

108

如果您使用的是进行简单替换的shell且SHELL_VAR变量不存在(或为空),则需要注意边界情况。将会发生以下翻译:

如果你使用的是进行简单替换的shell,以及SHELL_VAR变量不存在(或为空),那么你需要小心处理边缘情况。以下翻译将会发生:

if test $SHELL_VAR = yes; then        -->  if test = yes; then
if test x$SHELL_VAR = xyes; then      -->  if test x = xyes; then

这两者中,第一个会因为缺失test的第一个参数而产生错误。第二个则没有这个问题。

你的情况可以翻译如下:

if test "x$SHELL_VAR" = "xyes"; then  -->  if test "x" = "xyes"; then

至少对于符合POSIX标准的shell而言,x实际上是多余的,因为引号确保空参数和包含空格的参数都被解释为单个对象。


3
引用变量来比较空变量的情况会更加优雅,例如 test "$SHELL_VAR" = yes,而不是使用 xyes。对于选项处理,将顺序改为 'abc'="$1" 可能是另一个选项。 - Ciro Santilli OurBigBook.com
为什么test函数的第一个参数会缺失而不是成为空字符串?不同的shell处理方式有所不同吗? - phk
1
@phk:尝试使用export xxx=''if test $xxx = 1 ; then echo 2 ; fiexport xxx='yyy'if test $xxx = 1 ; then echo 2 ; fi。第一个if会出错,第二个不会。这是在bash中。 - paxdiablo
我的错误,我使用的是“printf”进行测试,它显然总是假定存在第二个参数。 - phk
但是哪些shell执行“简单替换”?我测试了BASH、DASH和KSH。它们都将空引号""视为一个空参数。因此,即使VAR为空,test "$VAR" = foo在它们中也是安全的。 - SnakE
1
@SnakE:与POSIX兼容的shell可以安全地执行此操作,我在我的结尾段落中已经说明了这一点。Jonathan Leffler的答案解释了为什么在旧的shell中可能会使用x - paxdiablo

25

其他人尚未提到的另一个原因与选项处理有关。 如果您编写:

if [ "$1" = "abc" ]; then ...

如果 $1 的值是 '-n',则 test 命令的语法是不明确的,无法确定您正在测试什么。在前面加上 'x' 可以防止出现引导破折号的问题。

你必须查看真正古老的shell才能找到一个没有支持 -n-z 的测试命令;Version 7 (1978) 的 test 命令包括它们。虽然这并不完全无关紧要——一些 Version 6 UNIX 的东西逃脱到了 BSD,但是现在,你很难在当前使用中找到任何古老的东西。

不在值周围使用双引号是危险的,就像其他一些人指出的那样。实际上,如果有可能文件名可能包含空格(MacOS X 和 Windows 都在某种程度上鼓励这种做法,而 Unix 一直支持它,尽管像 xargs 这样的工具使它变得更困难),那么每次使用文件名时都应该将其用双引号括起来。除非你负责该值(例如,在处理选项时,你在启动时将变量设置为“no”,当在命令行中包含标志时将其设置为“yes”) ,否则在证明它们安全之前不安全使用未引用的变量形式——对于许多目的,您可能也可以随时这样做。或者记录下来,如果用户试图处理具有空格名称的文件,您的脚本将会严重失败。(还有其他需要担心的字符——例如,反引号也可能非常令人讨厌。)


4
在你提供的第一个例子中,如果测试核心被正确引用,我认为就不会存在歧义了。即使有些值为空,比如 [ "-n" = "" ],在这种情况下也有三个参数。因此,“test”实现不应该进入 [ -n "$var" ] 分支(该分支仅需要 2 个参数)。就我个人而言,每当我遇到问题时,要么是因为 $var 周围缺少引号导致出现“=:期望一元运算符”的错误,要么是 -z 选项不存在,要么是在其内部使用 test 的复杂核心 [ ... ] 而不是在 shell 级别使用多个 [ test1 ] || [ test2 ] (正如 @Jay 所提到的)。 - user1556814
3
如果您可以依靠现代实现,或许您可以省略此技巧,但有时很难阅读您的注释。从历史上看,一些test的实现会做出奇怪的事情,而这种技巧可以防止这些奇怪的实现。现在您可能很幸运,不必担心这个问题。 - Jonathan Leffler
就目前而言:“但是现在,你很难找到任何古老的东西仍在使用中”,它甚至在macOS中使用。例如,在*/usr/libexec/locate.mklocatedb中:if [ X"$1" = "X-presort" ]; then*,虽然可能不完全是这样...我来到这里是因为我无论如何都想不起来它为什么被使用,尽管我知道过去曾经用过。 - Pryftan

14

我知道这个约定的原因有两个:

http://tldp.org/LDP/abs/html/comparison-ops.html

在复合测试中,即使引用字符串变量,可能也不足以满足要求。如果 $string 为空,则 [ -n "$string" -o "$a" = "$b" ] 可能会在某些 Bash 版本中出现错误。安全的方法是在可能为空的变量后面添加额外的字符,如 [ "x$string" != x -o "x$a" = "x$b" ] ("x's" 相互抵消)。

第二点,在除 Bash 外的其他 shell 中,特别是旧版本的 shell 中,测试空变量条件的选项如“-z”不存在,因此,以下语句可能会失败:

if [ -z "$SOME_VAR" ]; then
  echo "this variable is not defined"
fi

如果你的目标是要在各种UNIX环境中实现可移植性,而你不能确定默认shell是否为Bash以及它是否支持-z测试条件,那么在BASH中将可以很好地工作,但更安全的做法是使用if [ "x$SOME_VAR" = "x" ]这种形式,因为它始终具有预期的效果。本质上,这是一种查找空变量的旧Shell脚本技巧,尽管现在有更清晰的方法可用,但由于向后兼容性仍在使用。


对于第一个情况,我实际上会选择 [ -n "$string" ] || [ "$a" = "$b" ] - Weijun Zhou

5
我建议改为:
if test "yes" = "$SHELL_VAR"; then

由于它去除了丑陋的x,并且仍然解决了https://dev59.com/jXVC5IYBdhLWcg3w0EsD#174288中提到的问题,即$SHELL_VAR可能以-开头并被读取为选项。


2

我相信这是由于

与IT技术有关。
SHELLVAR=$(true)
if test $SHELLVAR  = "yes" ; then echo "yep" ; fi 

# bash: test: =: unary operator expected

除此之外,以及
if test $UNDEFINEDED = "yes" ; then echo "yep" ; fi
# bash: test: =: unary operator expected

并且
SHELLVAR=" hello" 
if test $SHELLVAR = "hello" ; then echo "yep" ; fi
# yep 

然而,这通常应该可以工作。
SHELLVAR=" hello"
if test "$SHELLVAR" = "hello" ; then echo "yep" ; fi 
#<no output>

但是当它在其他地方报错时,很难确定它在抱怨什么,我猜测。所以

SHELLVAR=" hello"
if test "x$SHELLVAR" = "xhello" ; then echo "yep" ; fi 

这样做可以起到同样的效果,但更容易调试。


-1

以前在DOS中,当SHELL_VAR可能未定义时,我会这样做。


-1

如果你不使用"x$SHELL_VAR"的方式,那么如果$SHELL_VAR未定义,你会得到一个关于"="不是单目运算符或类似的错误。


只有在引用不正确时才会出现这种情况;如果引用不正确,则您的shell脚本存在更大的问题,使用 x$FOO 也无法解决。 - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接