Shell 条件语句

背景

一些名词:

  • POSIX:一个规范,定义了一套命令行的API,所以是可移植的,理论上可以在各种 Shell 的实现上运行。
  • bash / dash / zsh:是 POSIX 的不同实现,各自有一些功能上的增强,包括一些 POSIX 之外的语法。

一般来说,Unix 系统都会有一个 /usr/bin/sh 的符号链接,指向当前系统真正的 Shell 实现,所以兼容 POSIX 的脚本都可以使用 /usr/bin/sh 来执行,所以大部分脚本的开头 Shebang 部分会写成 #!/usr/bin/sh

条件语句

简单的脚本基本上就是由一系列的命令组成,但是稍微复杂一点出现了逻辑分支的时候,就离不开条件语句了。

条件语句大致长这样:

if command1; then command2; elif command3; then command4; else command5; fi
# 或
if command1
then command2
elif command3
then command4
else command5
fi

按照 Shell 的惯例,一个命令返回 0 则表示 true,返回非 0 则表示出现了异常,相当于 false。简言之,0true,非 0false,这与大部分现代语言是相反的,所以初接触 Shell 的人可能会很不习惯。对应到上面的语句中,如果 command1 正常退出,即返回了 0,则 command2 会被执行。

在 Shell 里,if …then …elif …else … 分别都是一个语句,相邻的两个语句间必须有明确的分隔符,不然无法判断前一个语句的结束,因为 then 是可能作为一个字符串当做前一个 if command 的参数的。所以每个语句最后都需要一个 ; 或者换行。

看一个最简单的例子:

if ; then
  echo hi there
fi

这里 if 后面是个空语句,不会发生错误,所以是正常返回的,相当于 true,所以控制台会显示 hi there

判断语句

条件语句中离不开判断,Shell 中的判断和现代语言相比,不那么直观,甚至语法有一些奇怪,这里面还涉及一些历史原因,所以所以这里单独拿出来讨论一下。

test[ expression ]

test 是 POSIX 中定义的判断命令,理论上是被所有 Shell 支持的。

[test 的另一种写法,它要求有一个对应的 ] 结束符。

通过 man test 我们可以看到更详细的说明,大致用法如下:

test expression
# 或
[ expression ]

这里有几个值得注意的问题:

  • [] 的前后必须有空格,] 后面也可以有其他表示语句结束的符号,如 ];。原因是 Shell 接受的是空格分隔的一系列参数,所有的不被识别为关键字或者选项的参数都会被当做字符串或者命令,所以必须有一组空格分隔的 [] 才会被当做判断语句。这也是一个经常困扰新手的问题。
  • [] 间的内容依然是普通参数,所以如果出现了特殊符号,可能会导致语法错误,需要转义。
  • expression 中,参数如果为空或者包含空格,则必须有引号包裹,否则 Shell 会认为缺少参数或参数数量不对而报错,或忽略错误而返回意外的结果。所以我们经常会看到这样的写法:[ x$foo = x$bar ],前缀 x 就是为了确保这个变量执行后不是空字符串,当然也可以写成 [ "$foo" = "$bar" ]

[[ expression ]]

双层方括号的判断语句并不是 POSIX 中的规范,而是现代流行的各个 Shell 实现做的增强,所以它实际上也得到了各个 Shell 的广泛支持。

它的用法如下:

[[ expression ]]

[[ … ]] 的出现是为了改进判断的语法,所以它和 [ expression ] 有一些不同:

  • [[]] 间的内容不需要转义,即使为空或者包含空格,也可以被正确处理,如 [[ $foo == $bar ]]
  • 除此之外,语法上还有了一些增强,比如支持正则匹配,原生支持 &&|| 等操作。

值得注意的是:

  • 它与 [ … ] 一样,[[]] 也需要通过空格分隔成单独的参数。

(command)

和直接执行 command 一样,最终会返回退出时的 code,并根据是否为 0 来判断条件,0 表示 true

区别在于,(command) 会在子 Shell 中执行 command,好处是可以避免环境的相互污染。

这里圆括号里面并不需要额外的空格,大概是因为 ( 是一个标识语句开始的符号,后面的内容都会到子 Shell 中执行,而不需要将 () 当成参数来解析。

((expression))

这也是一个 POSIX 之外的扩展语法,用于返回 expression 的计算结果,和函数类似,然后将非 0 当做 true0 当做 false

这里的判断条件和 Shell 的惯例相反,和大部分其他语言一致,非 0 才是 true

总结

  • if …; then …; elif …; else …; fi 是 Shell 中的条件语句,每两个语句间一定要有 ; 或换行符分隔。
  • [ … ] 是 POSIX 语法,[] 和中间的内容都是独立的参数,需要确保参数的数量正确(通过引号、转义),支持的判断方式有限。
  • [[ … ]] 是 Shell 实现时增强的语法,[[]] 需要隔离成独立的参数,但是中间的内容无需引号和转义,而且可以支持正则表达式。
  • command(command) 是执行命令,取返回值来判断,0true
  • ((expression)) 是计算表达式,类似于函数,取返回值来判断,非 0true

参考


© 2020