Bash 零基础教程

Bash 条件语句

条件语句是编程逻辑的基础,它允许脚本根据特定条件是真(true)还是假(false)来执行不同的代码块。在 Bash 脚本编程中,我们使用 ifthenelseeliffi 关键字来构建这些条件结构。

本章将全面介绍条件语句的语法、不同类型的条件,以及如何在 Bash 脚本中高效地使用它们。

1. if 语句:基础语法

if 语句是 Bash 中最基础的条件执行形式。它允许你仅在特定条件为真时才执行某段代码。 其基本语法如下:

if [ condition ]; then
  # 如果条件为真,则执行此处的代码
fi
  • if: 开启条件语句的关键字。
  • [ condition ]: 需要评估的条件。非常重要的一点是,方括号内的条件两边必须要有空格。这里的 [ 实际上是一个命令,它是 test 命令的别名。
  • ;: 语句分隔符。当 then 和条件放在同一行时,必须加上分号。
  • then: 关键字,标记条件为真时要执行的代码块的开始。
  • # 代码...: 要执行的代码块。可以包含一条或多条 Bash 命令。
  • fi: 关键字,代表 if 语句的结束(if 倒过来拼写)。

示例:检查文件是否存在

#!/bin/bash
file="my_file.txt"

if [ -f "$file" ]; then
  echo "文件 '$file' 存在。"
fi

在这个例子中:

  • -f "$file" 是条件。-f 选项用来测试由变量 $file 指定的文件是否存在,并且是一个常规文件。
  • 如果该文件存在,控制台就会打印出 "文件 'my_file.txt' 存在。" 的消息。

2. else 语句

如果 if 语句中的条件为假,else 语句提供了一个备选的代码块来执行。其语法如下:

if [ condition ]; then
  # 如果条件为真,执行此处的代码
else
  # 如果条件为假,执行此处的代码
fi

示例:检查目录是否存在,不存在则创建它

#!/bin/bash
dir="my_directory"

if [ -d "$dir" ]; then
  echo "目录 '$dir' 已存在。"
else
  echo "目录 '$dir' 不存在。现在开始创建..."
  mkdir "$dir"
fi

在这个例子中:

  • -d "$dir" 是条件。-d 选项用来测试由变量 $dir 指定的目录是否存在。
  • 如果目录存在,打印一条消息。否则,打印另一条消息,并执行 mkdir 命令来创建该目录。

3. elif 语句

elif(else if 的缩写)语句允许你将多个条件链接在一起。如果前面的 ifelif 条件为假,它提供了一种测试额外条件的方法。其语法如下:

if [ condition1 ]; then
  # 如果 condition1 为真,执行此代码
elif [ condition2 ]; then
  # 如果 condition1 为假且 condition2 为真,执行此代码
else
  # 如果 condition1 和 condition2 都为假,执行此代码
fi

你可以编写多个 elif 语句来测试一系列条件。

示例:根据不同范围对数字进行评级

#!/bin/bash
num=75

if [ "$num" -gt 90 ]; then
  echo "优秀"
elif [ "$num" -gt 70 ]; then
  echo "良好"
elif [ "$num" -gt 50 ]; then
  echo "及格"
else
  echo "有待提高"
fi

在这个例子中:

  • -gt 是一个数值比较运算符(表示“大于” / greater than)。
  • 脚本会依次检查 $num 的值符合哪个范围,并打印出相应的消息。

4. 组合条件

Bash 允许你使用逻辑运算符来组合多个条件:

  • -a &&: 逻辑与(AND),所有条件都必须为真。
  • -o ||: 逻辑或(OR),至少有一个条件为真。
  • !: 逻辑非(NOT),反转条件的真假。

示例:检查文件是否存在且具有可读性

#!/bin/bash
file="my_file.txt"

if [ -f "$file" -a -r "$file" ]; then
  echo "文件 '$file' 存在并且可读。"
else
  echo "文件 '$file' 要么不存在,要么不可读。"
fi

在这个例子中:

  • -r "$file" 测试文件是否可读。
  • -a 运算符确保文件既要存在且要可读,才会执行 then 代码块。

你也可以使用 && 来编写相同的条件:

if [ -f "$file" ] && [ -r "$file" ]; then
  echo "文件 '$file' 存在并且可读。"
else
  echo "文件 '$file' 要么不存在,要么不可读。"
fi

示例:检查变量是否为空或者包含特定值

#!/bin/bash
variable=""

if [ -z "$variable" ] || [ "$variable" = "default" ]; then
  echo "该变量要么为空,要么其值为 'default'。"
fi

在这个例子中:

  • -z "$variable" 测试变量是否为空(长度为零)。
  • "$variable" = "default" 测试变量是否等于字符串 "default"。注意 = 号两边要有空格。
  • || 运算符确保只要其中任何一个条件为真,就会执行 then 代码块。

5. 测试命令的替代方案:[[ ]]

Bash 提供了一种使用双括号 [[ ]] 的条件表达式替代语法。与单方括号 [ ] 相比,这种语法具有几个优势:

  • 不需要对某些特殊字符进行转义。
  • 支持使用 =~ 进行正则表达式的模式匹配。
  • 不会执行单词拆分(Word splitting)和路径名扩展。

示例:结合正则表达式使用 [[ ]]

#!/bin/bash
string="hello world"

if [[ "$string" =~ "world" ]]; then
  echo "字符串中包含 'world'。"
fi

在这个例子中:

  • =~[[ ]] 内部的正则表达式匹配运算符。
  • 条件检查字符串 $string 是否包含子字符串 "world"。=~ 运算符右侧的内容会被解析为扩展正则表达式。

示例:使用 [[ ]] 避免引号带来的问题

#!/bin/bash
file="my file.txt"

# 如果文件不存在,单括号写法可能会报错
if [[ -f "$file" ]]; then
  echo "文件存在"
fi

# 上面的代码等价于使用 test ([]) 的下面这段代码:
if test -f "$file"; then
  echo "文件存在"
fi

使用 [[ ]] 时,你通常不需要担心包含空格的变量的引号问题,不过为了代码的清晰度,加上引号依然是个好习惯。然而,当使用 test 命令(即 [)时,加上引号是必须的,这样可以防止单词拆分和不可预料的行为。

6. 数值比较

在 Bash 中比较数值时,你应该在 [ ][[ ]] 中使用以下运算符:

  • -eq: 等于 (Equal to)
  • -ne: 不等于 (Not equal to)
  • -gt: 大于 (Greater than)
  • -ge: 大于或等于 (Greater than or equal to)
  • -lt: 小于 (Less than)
  • -le: 小于或等于 (Less than or equal to)

示例:比较两个数字

#!/bin/bash
num1=10
num2=20

if [ "$num1" -lt "$num2" ]; then
  echo "$num1 小于 $num2"
fi

7. 字符串比较

比较字符串时,你可以在 [ ][[ ]] 中使用以下运算符:

  • =: 等于
  • ==: 等于(与 = 相同)- 在 [[ ]] 中更常用
  • !=: 不等于
  • -z: 如果字符串为空(长度为零),则为真
  • -n: 如果字符串不为空,则为真

示例:比较两个字符串

#!/bin/bash
str1="hello"
str2="world"

if [ "$str1" != "$str2" ]; then
  echo "$str1 不等于 $str2"
fi

重要提示: 当在 [ ] 中使用 =!= 时,请务必用引号将你的变量括起来。如果变量中包含空格,这能避免单词拆分的问题。在 [[ ]] 中虽然通常不需要引号,但养成加引号的习惯依然很好。

8. 文件存在性与类型检查

Bash 提供了许多用于检查文件是否存在以及文件类型的运算符:

  • -e: 如果文件存在,则为真 (exists)
  • -f: 如果文件存在且是一个常规文件,则为真 (file)
  • -d: 如果文件存在且是一个目录,则为真 (directory)
  • -r: 如果文件存在且可读,则为真 (readable)
  • -w: 如果文件存在且可写,则为真 (writable)
  • -x: 如果文件存在且可执行,则为真 (executable)
  • -s: 如果文件存在且大小大于零,则为真 (size)

示例:检查文件是否可执行

#!/bin/bash
file="my_script.sh"

if [ -x "$file" ]; then
  echo "文件 '$file' 是可执行的。"
else
  echo "文件 '$file' 不可执行。"
fi

9. 退出状态码

在 Bash 中,每一个命令都会返回一个退出状态码(Exit Status),这是一个整数值,用来表示命令是否执行成功。零 (0) 表示成功,而任何非零值都表示失败。你可以使用 $? 变量来获取上一个执行命令的退出状态码。

示例:检查 mkdir 的退出状态码

#!/bin/bash
mkdir "new_directory"

if [ "$?" -eq 0 ]; then
  echo "目录创建成功。"
else
  echo "目录创建失败。"
fi

if 条件中直接使用命令是一种更简洁的写法:

#!/bin/bash

if mkdir "new_directory"; then
  echo "目录创建成功。"
else
  echo "目录创建失败。"
fi

在这个例子中,if 语句直接执行 mkdir 命令。如果 mkdir 成功(返回退出状态码 0),则执行 then 代码块。如果 mkdir 失败(返回非零退出状态码),则执行 else 代码块。这是在 Bash 脚本中检查错误的一种非常常见且高效的方法。

10. 实战:在系统管理脚本中集成错误检查

让我们回顾一下之前模块中的系统管理脚本,并利用条件语句添加错误检查功能。假设这个脚本需要创建一个新的用户账户。

#!/bin/bash
username="newuser"

# 检查用户是否已经存在
if id "$username" &>/dev/null; then
  echo "错误:用户 '$username' 已存在。"
  exit 1  # 带有错误码退出脚本
fi

# 创建用户
useradd "$username"

# 检查用户创建是否成功
if [ "$?" -ne 0 ]; then
  echo "错误:创建用户 '$username' 失败。"
  exit 1
fi

echo "用户 '$username' 创建成功。"

# 设置密码(在真实的脚本中,请使用更安全的方法)
echo "$username:password" | chpasswd

# 检查密码是否设置成功
if [ "$?" -ne 0 ]; then
  echo "警告:为用户 '$username' 设置密码失败。"
  # 我们这里不退出脚本,因为用户已经创建成功了,但我们需要发出警告
fi

echo "用户 '$username' 密码设置完毕。"
exit 0 # 以成功状态退出脚本

关键改进点解析:

  1. id "$username" &>/dev/null: 这个命令尝试获取用户信息。&>/dev/null 会将标准输出 (stdout) 和标准错误 (stderr) 都重定向到 /dev/null,相当于让命令“静音”。如果用户存在,id 返回 0(成功);如果用户不存在,返回非零值(失败)。
  2. exit 1: 这个命令会以错误码 1 终止脚本运行,表示发生了错误。使用恰当的错误码退出非常重要,这样调用该脚本的其他程序或系统就能探测到失败的情况。
  3. 密码设置警告: 如果设置密码失败,脚本现在会发出警告,但不会退出。这是因为用户其实已经创建成功了,密码设置失败虽然重要,但不是一个需要中断整个脚本的致命错误。在生产环境中,你应该使用更安全的密码管理方案。
  4. exit 0: 脚本最后以成功码 (0) 退出,表示如果没有遇到错误,脚本已顺利执行完毕。

这个示例展示了如何运用条件语句和退出状态码为你的 Bash 脚本添加健壮的错误检查机制,让它们变得更可靠、更容易调试。