# 流程控制
# 语句块
- 用括号
( )
或花括号{ }
可以声明一个语句块,如下:( echo Hello echo World ) &> /dev/null
[root@CentOS ~]# ( b=42 ); echo $b # 括号的语句块会创建一个子 Shell 去执行 [root@CentOS ~]# { a=42; }; echo $a # 花括号的语句块会放在当前 Shell 中执行 42
# 逻辑运算符
!
:非运算符。- 将它放到一条命令之前,会对该命令的返回码进行逻辑非运算。
- 例:
[root@CentOS ~]# ! [root@CentOS ~]# echo $? 1 [root@CentOS ~]# ! echo Hello Hello [root@CentOS ~]# echo $? # echo 命令执行成功,返回码本来为 0 ,但被 ! 变成了 1 1 [root@CentOS ~]# ! ech Hello -bash: ech: command not found [root@CentOS ~]# echo $? # ech 命令执行失败,返回码本来为非 0 ,但被 ! 变成了 0 0
&&
:与运算符。- 用它连接两条命令,则前一条命令返回码为 0 时才会执行后一条命令。
||
:或运算符。- 用它连接两条命令,则前一条命令返回码为非 0 时才会执行后一条命令。
- 例:
ls f1 && touch f2
:若存在文件 f1 ,则创建文件 f2 。ls f1 || touch f1
:若不存在文件 f1 ,则创建文件 f1 。ls f1 && echo True || echo False
:若第一条命令返回码为 0 ,则执行第二条命令,否则执行第三条命令。ls f1 || touch f1 && touch f2
:若第一条命令返回码为 0 ,则执行第三条命令;若第一条命令返回码为非 0 ,则执行第二条命令,然后根据第二条命令的返回码,判断是否执行第三条命令。
# 条件表达式
- Shell 中,任意命令都可以用作条件表达式。如果该命令的返回码为 0 ,则视作逻辑真;如果为非 0 ,则视作逻辑假。
- 不过最常用作条件表达式的命令是
test
、[ ]
、[[ ]]
。
# test
:用于检测一个表达式的真假。如果表达式为真,则 test 命令的返回码为 0 ,否则为 1 。
命令:
$ test [expression] # 判断表达式的真假 ! [expression] # 非运算
expression 有多种:
-n string # nonzero ,检测字符串是否不为空,即长度不为 0 。可以省略 -n -z string # zero ,检测字符串是否为空 Hi == hi # 检测两个字符串是否相同 Hi != hi # 检测两个字符串是否不同 1 -eq 2 # 检测两个数是否相等 1 -ne 2 # 不等于 1 -gt 2 # 大于(greater than) 1 -ge 2 # 大于等于 1 -lt 2 # 小于(lower than) 1 -le 2 # 小于等于 -e <path> # 检测该路径是否存在 -f <path> # 检测该路径是否存在,并且为普通文件 -d <path> # 检测该路径是否存在,并且为目录 -r <path> # 检测该路径是否存在,并且有可读权限。类似的还有-w、-x
例:
[root@CentOS ~]# test ; echo $? # 字符串为空,因此表达式为假,返回码为 1 1 [root@CentOS ~]# test 0; echo $? # 字符串内容为 0 ,不为空,因此表达式为真,返回码为 0 0 [root@CentOS ~]# test ! Hello; echo $? # 字符串不为空,但进行了非运算 1
[root@CentOS ~]# test Hello && echo True || echo False True [root@CentOS ~]# test Hi == hi && echo True || echo False False [root@CentOS ~]# test 3 == 3.0 && echo True || echo False # 注意 == 不能用于检测数值是否相等 False
test 命令中可以用 -a、-o、! 三个符号构成与或非表达式。如下:
test -d /tmp -a -r /tmp # 检测 /tmp 目录是否存在,且拥有可读权限 test -r /tmp -o -w /tmp # 检测是否拥有可读或可写权限 test ! "" # 检测字符串是否不为空
当 test 命令不使用命令选项时:
- 如果表达式为空,则检测结果为假。如下:
[root@CentOS ~]# test && echo True || echo False False
- 如果表达式为数值,则检测结果总是为真。如下:
[root@CentOS ~]# test 0 && echo True || echo False True
- 如果表达式为字符串,则检测结果为真。但如果是空字符串,则检测结果为假。如下:
[root@CentOS ~]# test hello && echo True || echo False True [root@CentOS ~]# test "" && echo True || echo False False
- 注意表达式只能输入一个,不能输入多个。如下:
[root@CentOS ~]# FILES=`ls` [root@CentOS ~]# test $FILES # 输入为一个数组,导致报错 -bash: test: too many arguments [root@CentOS ~]# test "$FILES" && echo True || echo False # 将输入从数组类型转换成字符串类型 True
- 如果表达式为空,则检测结果为假。如下:
# 中括号
[ ]
[
是一个内置命令,必须与]
成对使用,用法相当于 test 命令。- 例:
[root@CentOS ~]# [ -d /tmp ] && echo True || echo False True [root@CentOS ~]# [ Hi == hi ] && echo True || echo False False [root@CentOS ~]# [ Hi != hi ] && echo True || echo False True
- 注意运算符、中括号的前后要用空格分隔,以免被 Shell 解释器视作同一个字符串。如下:
[root@CentOS ~]# [ Hi==hi ] && echo True || echo False # 此时将 Hi==hi 看作一个字符串,所以结果为 True True [root@CentOS ~]# [Hi == hi ] && echo True || echo False # 此时将 [Hi 看作一个命令名 -bash: [Hi: command not found False
# 双中括号
[[ ]]
[[
是一个 Shell 关键字,必须与]]
成对使用。[[ ]]
的功能比[ ]
更多,包括:- 双中括号内支持使用运算符
&&
、||
。 - 双中括号内支持使用运算符
=~
进行字符串的正则匹配。
- 双中括号内支持使用运算符
- 例:
[root@CentOS ~]# [[ 1 != 2 && 2 != 3 ]] && echo True || echo False True [root@CentOS ~]# [[ Hi =~ H.* ]] && echo True || echo False # =~ 用于判断右侧的 pattern 是否正则匹配左侧的字符串 True
# 选择结构
# if
单重 if :
if <条件表达式> then <语句块> fi
- 加上分号作为分隔符,就可以写成单行:
if ...; then ...; fi
- 可以用任意命令作为条件表达式,如下:
[root@CentOS ~]# if echo Hello ; then echo True; else echo False; fi Hello True [root@CentOS ~]# if test Hello ; then echo True; else echo False; fi True [root@CentOS ~]# if [ Hello ] ; then echo True; else echo False; fi True [root@CentOS ~]# if [ -d /tmp ] ; then echo True; else echo False; fi True
- 注意:条件表达式执行出错时,会被 if 视作逻辑假,然后执行 else 语句块。如下:
[root@CentOS ~]# if ech Hello ; then echo True; else echo False; fi -bash: ech: command not found False
- 可以通过逻辑运算符组合多个条件,它们的最终结果,决定了 if 的逻辑真假。例:
if [ "${FLAG_1,,}" = true ] && [ "${FLAG_2,,}" = true ] || [ "${FLAG_3,,}" = true ] then ... else ... fi
- 加上分号作为分隔符,就可以写成单行:
双重 if :
if ...; then ... else ... fi
多重 if :
if ...; then ... elif ...; then ... else ... fi
if 语句以 fi 结尾,case 语句以 esac 结尾(都是将名字反过来写表示结束),不过 while 语句以 done 结尾。
# case
- 格式:
case $name in "hello") # 每种情况的值以右括号 ) 结尾。这里是当 name 等于 hello 时执行该语句块 语句块 ;; # 每个语句块以两个分号 ;; 结尾 "") # 当 name 为空字符串时执行该语句块 语句块 ;; *) # 匹配剩下的所有情况 语句块 ;; esac
# 循环结构
- for、while、until 循环体内都支持使用
break
、continue
。
# for
C 风格的写法:
for (( i=0 ; i<10 ; i++ )) # 条件循环 do echo hello done
遍历风格的写法:
[root@CentOS ~]# for i in one two three # 遍历多个字符串 > do > echo $i, > done one, two, three, [root@CentOS ~]# for i in one two three; do echo $i,; done # 遍历的多个字符串之间,可以用任意个空格分隔 one, two, three, [root@CentOS ~]# for i in "one two three"; do echo $i,; done # 这是只遍历一个字符串 one two three, [root@CentOS ~]# for i in "one"; do echo $i,; done # 遍历一个字符串时,并不会像 Python 那样遍历单个字符 one,
for i in {1..5} # 遍历一个等差数列 for i in `ls /root` # 遍历一组文件名
例:执行一个命令,如果执行失败则最多重试 3 次
for i in {1..3} do <command> && break done
# while
- 格式:
while [ ... ] # 当条件表达式为真时进入循环 do ... done
- 例:逐行遍历一个文件
cat /etc/os-release | while IFS= read -r line do echo "$line" done
- IFS 是 shell 的内部变量,称为内部字段分隔符(Internal Field Separator),默认值为
\x0b\t\n
。- 将它赋值为空,用于保留每行开头、末尾的空字符。
- IFS 是 shell 的内部变量,称为内部字段分隔符(Internal Field Separator),默认值为
# until
- 格式:
until [ ... ] # 当条件表达式为真时退出循环 do ... done
# 函数
- 定义函数的一般格式:
name() { block }
- 有些 Shell 解释器使用关键字 function 定义函数:
function name() { block }
function name { block }
- 例:创建局部变量
fun1() { a=1 # 在函数内创建的变量默认为全局变量 local b=2 # 可用 local 创建局部变量 # return 0 # 可用 return 返回一个整数。如果没有 return ,则默认将函数执行的最后一个命令的返回值作为函数的返回值 } [root@CentOS ~]# fun1 # 定义函数之后,便可调用函数名 [root@CentOS ~]# echo $a [root@CentOS ~]# echo $b
- 当函数运行结束时,会自动销毁所有局部变量。
- 读取一个变量时,如果同时存在同名的局部变量与全局变量,则优先读取局部变量。
- 例:给函数输入参数,并检查输入参数的数量
log() { [ $# -ge 2 ] || { echo "Usage: ${FUNCNAME[0]} <level> <message>" return 1 } local level=$1 local message=${@:2} echo `date +"%Y/%m/%d %H:%M:%S"` $level $message } [root@CentOS ~]# log Usage: log <level> <message> [root@CentOS ~]# log INFO testing 2022/01/01 12:01:31 INFO testing
# 相关命令
# sleep
$ sleep <n>[suffix]... # 让终端睡眠一定时间,这会阻塞等待
- suffix 可以是 s、m、h、d ,分别代表秒、分钟、小时、天。
- 例:
sleep 1 sleep 1d sleep 1m 10s
# true、false
- 执行 true、false 命令时,其后的内容会被忽略,相当于注释。
$ true [line] # 该命令的返回码总是 0
$ false [line] # 该命令的返回码总是 1
- true 命令的别名为 : 。
- 例:
[root@CentOS ~]# true touch f1 && ls f1 ls: cannot access 'f1': No such file or directory [root@CentOS ~]# true `touch f1` && ls f1 # 嵌套的命令依然会被执行 f1 [root@CentOS ~]# true ; echo $? 0 [root@CentOS ~]# ! true ; echo $? 1