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


72

我经常在使用自动工具(autoconf,automake)的项目的构建脚本中看到这种情况。当有人要检查shell变量的值时,他们经常使用以下惯用法:

if test "x$SHELL_VAR" = "xyes"; then
...

与像这样简单地检查值相比,这样做有什么好处:

if test $SHELL_VAR = "yes"; then
...

我认为一定有某种原因使我经常看到此消息,但我无法弄清楚它是什么。


2
另请参见x中的Shell脚本用途"x$Variable"。这个问题是这个问题的重复。
乔纳森·勒夫勒

2
另请参见bash测试中是否包含空字符串X""。这个问题实际上也是这个问题的重复,或者涵盖了大多数相同的问题。两个重复项都有很好的答案。
乔纳森·莱夫勒

Answers:


92

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

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

由于第一个参数totest丢失了,因此第一个将产生错误。第二个没有那个问题。

您的案例翻译如下:

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

x,至少对于符合POSIX的壳,实际上是多余的,因为引号随之而来的是两个空的参数和一个容纳空间被解释为单个对象。


3
引用是否比解决空var边的情况test "$SHELL_VAR" = yes更优雅xyes?对于期权处理,更改订单'abc' = "$1"会是另一种选择吗?
西罗Santilli郝海东冠状病六四事件法轮功

为什么第一个参数test丢失而不会变成空字符串?不同的外壳处理方式是否不同?
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

1
@SnakE:兼容POSIX的shell可以安全地执行此操作,我在最后的段落中已经指出。乔纳森·莱夫勒(Jonathan Leffler)的答案解释了为什么x可能在较旧的外壳中使用了它们。
paxdiablo

22

没有人提及的另一个原因与期权处理有关。如果您写:

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

并且$ 1的值为'-n',测试命令的语法不明确;不清楚您正在测试什么。前面的“ x”可防止前划线引起麻烦。

您必须查看真正古老的shell才能找到其中一个test命令不支持-nor的外壳-z;版本7(1978)test命令包括它们。并不是很无关紧要-一些版本6 UNIX的内容已经泄漏到BSD中,但是如今,要找到当前使用的古老内容非常困难。

正如许多其他人指出的那样,不对值使用双引号是很危险的。确实,如果文件名中可能包含空格(MacOS X和Windows都在一定程度上鼓励这样做,并且Unix一直支持它,尽管诸如xargs加大难度),那么您也应在每次使用文件名时将文件名都用双引号引起来。除非您负责该值(例如,在选项处理期间,并且在启动时将变量设置为“ no”,在命令行中包含标志时将变量设置为“ yes”),否则使用不带引号的变量形式是不安全的直到您证明它们是安全的-并且您不妨一直将其用于多种用途。或者记录一下,如果用户尝试处理名称中带有空格的文件,则脚本将严重失败。(还有其他角色也要担心,例如反引号也可能很讨厌。)


3
我要说的是,如您的第一个示例中正确引用测试核心时,没有歧义。在这种情况下,即使某些值为空,也存在3个参数,例如[“ -n” =“”]。因此,test实现不应输入[-n“ $ var”]分支大小写(仅需要2个参数)。就我个人而言,每次遇到问题时,要么与$ var周围的引号缺失有关,导致错误“ =:期望一元运算符”,要么-z选项不存在,或者test在其核心[...]内部而不是复杂地使用使用多个[test1] || [test2]在shell级别(如@Jay所述)。
user1556814 15/10/28

2
如果您可以依靠现代的实现,虽然有时很难阅读您的评论,但也许您可以摆脱现实。从历史上看,某些实现test确实做了怪异的事情,并且该技术可以防止那些怪异的实现。您可能很幸运,这些天不必担心它。
Jonathan Leffler

9

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

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的”取消)。

其次,在除Bash以外的其他shell中,尤其是较旧的shell中,不存在用于测试空变量的测试条件(例如“ -z”),因此:

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

如果您希望在各种UNIX环境中实现可移植性,而在这些环境中您不能确定默认的shell是Bash且它是否支持-z测试条件,则使用BASH会更安全,如果[ x $ SOME_VAR“ =” x“],因为那样总是可以达到预期的效果。从本质上讲,这是一个用于查找空变量的旧Shell脚本技巧,尽管有更清洁的方法可用,但今天它仍用于向后兼容。


对于第一种情况我真的去[ -n "$string" ] || [ "$a" = "$b" ]
卫军周


2

我相信这是由于

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 

效果也一样,但更容易调试。


0

当SHELL_VAR可能未定义时,我曾经在DOS中这样做。


0

如果您不执行“ x $ SHELL_VAR”操作,那么如果未定义$ SHELL_VAR,则会收到有关“ =不是单子运算符或类似信息”的错误。


这只有在您引用不正确的情况下才是正确的。如果引用不正确,则说明您的shell脚本存在较大的问题,x$FOO无法使用解决。
查尔斯·达菲
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.