本文以 Bash 为准。代码中的方括号、分号和引号都使用半角字符,复制时别让中文标点来偷袭。

条件测试语句

基本语法

(1)test <条件>

(2)[ <条件> ] 需要注意方括号与条件之间要有空格

结果:条件成立时,测试命令的退出状态为 0;不成立时为 1,测试写法错误时还可能返回其他非零状态。

仅当条件是单个字符串时,才按“非空为真、空为假”判断。这里的 0 指退出状态;在 (( 算术表达式 )) 中,则是表达式数值非零为真、零为假,别混在一起。

1
test homo; echo $? #输出0,因为homo是非空字符串

文件测试

配合操作符,可以实现文件或者目录类型的检测。

例如,检测当前路径下的 homo 是不是目录?

1
2
3
touch homo
test -d homo; echo $? #在当前目录原先没有homo的情况下,输出1
#或使用 [ -d homo ]; echo $?

以下是文件测试操作符与相应说明的对应关系

操作符 说明
-d 测试是否为目录(Directory)
-e 测试目录或文件是否存在(Exist)
-f 测试是否为普通文件(File)
-r 测试当前用户是否可读(read)
-w 测试当前用户是否可写(write)
-x 测试当前用户是否可执行(execute)

整数测试

整数测试通常用于数值之间的比较,在一些逻辑关系的判断中有应用。

例如判断 1+1+5+4 与 1+9+1 是否相等,可以这么写

1
2
[ "$((1+1+5+4))" -eq "$((1+9+1))" ]; echo $? #输出0,都是11

以下是关系测试操作符与相应说明的对应关系

操作符 关系运算符 说明
-eq == 等于(Equal)
-ne != 不等于(Not Equal)
-gt > 大于(Greater Than)
-lt < 小于(Lesser Than)
-le <= 小于或等于(Lesser or Equal)
-ge >= 大于或等于(Greater or Equal)

字符串测试

字符串测试操作符的作用包括比较字符串是否相同、测试字符串的长度是否为0。

格式为:[ "$str1" = "$str2" ];测试长度时写成 [ -z "$str" ] 或 [ -n "$str" ]。给变量加双引号,空字符串和带空格的内容才不会拆坏测试语句。

以下是字符串测试操作符与相应说明的对应关系

操作符 含义
-z 判断字符串长度是否为 0
-n 判断字符串长度是否为 非0
!= 判断两边字符串是否 不相等
= 判断两边字符串是否 相等

逻辑运算符

在Shell条件测试中,使用逻辑运算符实现复杂的条件测试,逻辑运算符用于组合条件,! 则对条件取反。

格式为:[ 条件1 ] && [ 条件2 ] 或 [ 条件1 ] || [ 条件2 ]。

注意:旧写法 -a 和 -o 放在 [ ] 里面用,但复杂条件容易产生歧义,建议拆成多个测试后用 &&、|| 连接。Bash 的 [[ 条件1 && 条件2 ]] 也支持在内部使用 &&、||。

以下是逻辑运算操作符与相应说明的对应关系

操作符 含义
-a 或 && 与
-o 或 || 或
! 否

下面的流程控制语法使用“条件表达式”“代码块”等文字占位;实际使用时需替换为具体条件或命令。循环示意中的 name 是循环变量。

if条件

很常见的判断语句,不过要注意Shell中的流程控制不能为空

即如果 else 分支没有语句执行,就不需要写;如果要临时占位,可以写 :(空命令)。

if单分支

1
2
3
if [ 条件表达式 ];then
代码块
fi

if双分支

1
2
3
4
5
6
if [ 条件表达式 ]
then
代码块1
else
代码块2
fi

if多分支

1
2
3
4
5
6
7
8
9
if [ 条件表达式1 ];then
代码块1
elif [ 条件表达式2 ];then
代码块2
elif [ 条件表达式3 ];then
代码块3
else
代码块4
fi

case条件

程序会获取case语句中的变量值

  • 如果满足条件表达式1,则执行代码块 1

  • 如果满足条件表达式2,则执行代码块2

  • 如果满足条件表达式3,则执行代码块3

  • 执行到双分号 ;; ,则停止

  • 如果前面的模式都不匹配,则执行 *) 后面的代码块;它应放在最后作为默认分支

    只要满足一个条件表达式就会跳出case语句主体,执行esac字符后面的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case 变量值 in
条件表达式1)
代码块1
;;
条件表达式2)
代码块2
;;
条件表达式3)
代码块3
;;
*)
无匹配后代码块
;;
esac

case 中使用的是 Shell 通配模式,不是正则表达式,也不是数值条件测试。匹配符如下表所示

条件表达式 说明
* 任意长度的字符串(包括空字符串)
? 任意单个字符
[abc] a、b或c其中之一
[a-n] 从a到n的任一字符
| 多重选择

for循环

语法

for 循环依次取列表中的每一项,赋给循环变量,然后执行一次循环体;列表为空时不执行。

in 的取值列表支持替换、字符串和文件名;不用取值列表将使用命令行的位置参数

通常写法

1
2
3
4
for name in 值1 值2
do
命令
done

一行写法

1
for name in 值1 值2; do 命令; done

💡提醒:写成一行(在终端直接执行)时,需要记得加 ; 符号。

例子

列表不长时,直接交给 for 就行:

1
2
3
for num in 114514 1919 810; do
echo "本轮数字:$num"
done

while循环

用于不断执行一系列命令,也用于从输入文件中读取数据

语法

常规写法

1
2
3
4
while [ 条件表达式 ]
do
[代码块]
done

无限循环

1
2
3
4
while : #也可写成while true
do
[循环体]
done

例子

1
2
3
4
5
6
7
8
#!/bin/bash
#读取键盘信息
echo "在空行处按下 Ctrl+D 退出"
echo -n '你想吃啥: '
while IFS= read -r FOOD
do
echo "老板,来份:${FOOD}~"
done

批量创建用户

脚本将读取用户清单文件(格式:用户名 密码,用空白分隔),据此创建用户并配置密码。for 适合遍历已经准备好的列表;逐行处理文件时,使用下面的 while read 更合适,可以保留空行,也避免命令替换后发生额外的通配符展开。

下面的脚本面向 Linux,需要 root 权限以及 useradd、chpasswd 命令;会实际创建用户。清单中每行两个字段,用户名和密码不含空白或冒号,清单里保存的是明文密码,应限制文件访问权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/bin/bash
#判断脚本是否有参
if [ "$#" -ne 1 ]; then
echo "使用方法:$0 用户名单文件"
exit 1
fi
#判断用户名单是否为可读的普通文件
if [ ! -f "$1" ] || [ ! -r "$1" ]; then
echo "错误:用户名单$1不是可读的普通文件"
exit 1
fi
if [ "$EUID" -ne 0 ]; then
echo "错误:请使用root权限运行"
exit 1
fi
failed=0
#逐行读取;额外字段会归入extra,用来发现格式错误
while read -r user pass extra || [ -n "$user$pass$extra" ]; do
if [ -z "$user" ]; then
echo "清单出现空行,跳过创建。"
continue
fi
if [ -z "$pass" ] || [ -n "$extra" ] || [[ "$user$pass" == *:* ]]; then
echo "错误:用户${user}所在行的格式不正确"
failed=1
continue
fi
if id "$user" >/dev/null 2>&1; then
echo "提示:用户${user}已经存在!"
continue
fi
if useradd -- "$user"; then
echo "用户${user}已创建完毕!"
if printf '%s:%s\n' "$user" "$pass" | chpasswd; then
echo "用户${user}的密码配置完毕!"
else
echo "错误:用户${user}已创建,但密码配置失败"
failed=1
fi
else
echo "错误:用户${user}创建失败"
failed=1
fi
done < "$1"
if [ "$failed" -eq 0 ]; then
echo "提示:用户清单处理完毕,已有用户已跳过。"
else
echo "提示:用户清单处理完毕,但部分操作失败,请查看上方提示。"
fi
exit "$failed"

原来的 echo "密码 | passwd ..." 只会打印文本,不会执行管道。这里使用 chpasswd 接收 用户名:密码;某些 Linux 发行版的 passwd --stdin 也能读取密码,但不是通用选项。

until循环

与 while 循环相反,until 在条件为假时执行循环体,在条件为真时停止。它同样先检查条件,不保证至少执行一次。

语法

如果条件表达式的返回值为 false,则继续执行循环体内的语句,否则跳出循环。

1
2
3
4
until [ 条件表达式 ]
do
[循环体]
done

例子

1
2
3
4
5
6
7
8
#!/bin/bash
# 遍历输出0-9
a=0
until [ "$a" -ge 10 ]
do
echo ${a}
a=$((a + 1))
done

跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环。break 默认退出当前一层循环;continue 跳过本轮剩余语句,开始下一轮。嵌套循环中可以使用 break 2 退出两层,或 continue 2 继续外层循环的下一轮。