17

Bash 条件判断

 5 years ago
source link: https://harttle.land/2018/10/23/bash-test.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

其实本文不是介绍 条件分支语法 的, 只介绍测试(test)语法,也就是说:本文只介绍如何写条件语法中的 表达式 。 其实本文介绍的内容也并未针对 Bash,多数 Shell 都是适用的。

与其他编程语言类似, if 的条件可以接受任何 表达式 ,计算为真就进入分支。 只是 在 Bash 中表达式是一个命令调用

真与假

在 POSIX 标准下,程序的 退出状态 为零表示成功,非零表示失败。 而 Bash 的 表达式 就是命令调用,因此 0 为真(命令调用成功)1 为假(命令调用失败)。

> true
> echo $?   # 查看上一个命令的退出状态
0
> false
> echo $?
1

有些 Shell 中 true 是 built-in 命令,有些 Shell 中是 /usr/bin/true 二进制文件,但它们都会给出退出码零。

() subshell

如果命令很复杂可能会破坏 if 的结构,这时可以通过 ( expression ) 来强制在 subshell 中执行。 其中 ()Shell 特殊字符 。 没错,跟管道一样这会开一个 subshell。这里提一下 subshell 和子进程的区别: subshell 是一种特殊的子进程,可以访问父进程的任何变量(不需要 export )。下面的例子来自 wooledge.org

unset a; a=1
(echo "a is $a in the subshell")            # a is 1 in the subshell
sh -c 'echo "a is $a in the child shell"'   # a is  in the child shell

但 subshell 仍然是一种子进程,这意味着 subshell 中环境变量的定义和赋值对父 shell 是不可见的。 这一点是 subshell 的主要用途。

test 命令

test 命令可以用来测试文件是否存在、文件类型,也可以进行字符串判等、数字比较等操作。 在测试为真时 test 命令的退出码为 0,测试为假时退出码为 1,发生错误时大于 1。

使用示例

多数 Shell 实现中它们都属于 built-in 命令,它有两种使用方式:

test expression
[ expression ]

例如:

if test -e harttle.png; then
    echo file exists
fi

等价于:

if [ -e harttle.png ]; then
    echo file exists
fi

注意第二种用法中 [ 是一个命令, expression] 是它的两个参数。 因此如果 [ 后没有空格会发生 command not found 错误。

常用参数

下面介绍常见的几种用法,完整列表请 man testman [

文件判断

可以检查文件是否存在,文件类型,权限状态等。下面常见的文件判断参数:

-e file: file 存在
-d file: file 是一个目录
-f file: file 是一个普通文件(regular file)
-s file: file 非空(大小不为零)
-w file: file 是否有可写标志
-x file: file 是否有可执行标志

字符串判断

主要包括字符串长度判断,和字符串判等两类:

-n string: 字符串长度非零
-z string: 字符串长度为零
string1 = string2: 字符串相等
string1 != string2: 字符串相等
string: 字符串不是null时为真,否则为假

数字判断

数字测试参数包括:

num1 -eq num2: 等于
num1 -ne num2: 不等于
num1 -le num2: 小于等于
num1 -lt num2: 小于
num1 -ge num2: 大于等于
num1 -gt num2: 大于

这里有一个陷阱,既然 num1 , -gt , num2 都是以参数的形式传递给 test 命令的, 那么如果 参数替换 的结果是空,那么就会因为缺少参数而崩掉。例如:

> a=
> test 1 -eq $a
zsh: parse error: condition expected: 1

所以需要引号包裹所有变量来保证参数总是存在的,字符串判断也需要一样的处理。

> a=
> test 1 -eq "$a"
> echo $?
1

逻辑运算

当然,与( -a )或( -o )非( ! )都是支持的。但括号要转义,因为括号是 Shell 特殊字符,用来开启子 Shell 的。 一个复杂的例子如下:

if [ ! \( -f harttle.png \) ]; then
    echo file not found
fi

需要强调的仍然是 注意空格 !即使 \( 也是需要单独一个参数传递给 [ 命令。 下面的代码会产生错误: [: (-f: unary operator expected , 因为 ! 是单目运算符(Unary Operator),但后面跟着的是两个参数: \(-fharttle.png\)

if [ ! \(-f harttle.png\) ]; then
    echo file not found
fi

[[ 条件表达式

可以把 [[ expression ]] 看做是 [ 的现代版本,它与 test 命令有几乎一样的参数。 但它是一个 新的语法结构 ,不再是一个普通的 test 命令,因此不受制与 Shell 的 参数展开 ,比如 不需要用引号包裹所有变量,也支持类似 &&|| 这样的逻辑操作而不需要用类似 -a-o 这样的参数。 例如:

if [[ $string1 == 'harttle' ]]; then
    echo The author for this article is harttle
fi

算术展开(Arithmetic Expansion)

算术展开 的语法是 $((expression)) ,这是从其他编程语言来的人最顺手的算术操作方式。与 test 命令相比,

  • C 风格的算术操作语法
  • Shell 会把 expression 内容的每一项都当做就像被引号包裹了一样。
  • 会依次进行 参数展开命令替换 ,和 引号移除

因此可以不需要像 test 命令那样用引号包裹所有变量,也不需要注意空格,也可以写 C 风格的操作:

> a=2
> echo $(($a+3))
5

这只是一种 shell 展开并没有 subshell,因此在括号内进行的赋值会直接反映到当前的变量上。 需要注意的是 C 风格的判等 1 为 True,0 为 False。因此用作 if 命令的条件时需要反过来:

if test $(( 1 == 1 )) -eq 1; then
    echo one equals one
fi

是不是很变态?是的。因为 C 风格与 Shell 风格就是不同,下面介绍的 [[ 条件表达式 更加 Bash 风格,用起来表现地更一致。

总结

  • Shell 中的 if 通过命令的退出码来确定成功与否,而成功(True)的退出码是零。
  • test 是最基本的 Shell 风格的判断命令,它是一个命令所以要命令行参数的方式去使用,要小心 参数替换
  • [[ expression ]] 是现代版的 test ,是一个语法构造而非命令。因此在传参上可以舒服很多。
  • 如果你是来自 C 的程序员, 算术展开 会很适合你进行任何算术操作,包括条件判断。

需要注意的是,所有空格仍然需要保留,因为它仍然依赖 Bash 的词法解析。 更多使用方式请参考 Bash Hackers Wiki


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK