变量的数值计算实践
1 算术运算符
如果要执行算术运算,就会离不开各种运算符号,和其他编程语言类似,Shell 也有很多算术运算符。
下面就给大家介绍一下常见的 Shell 算术运算符:
- +、- ,一元正号和负号。
- +、-,加法和减法。
- *、/、%,乘法、除法、取余(取模)。
- **,幂运算。
- ++、--,增加及减少,可前置也可放在变量结尾。
- !、&&、||,逻辑非(取反)、逻辑与(and)、逻辑或(or)。
- <、<=、>、>=,比较符号(小于、小于等于、大于、大于等于)。
- ==、!=、=,比较符号(相等、不相等,对于字符串也可以表示相当于)。
- <<、>>,向左移位、向右移位。
- ~、|、&、^,按位取反、按位异或、按位与、按位。
- =、+=、-=、*=、/=、%= ,赋值运算符,例如
a+=1
相当于a=a+1
,a-=1
相当于a=a-1
。
Shell 中常见的算术运算命令:
- (()),用于整数运算的常用运算符,效率很高。
- let ,用于整数运算,类似于
(())
。 - expr,可用于整数运算,但还有很多其他的额外功能。
- bc,Linux下的一个计算器程序(适合整数及小数运算)。
- $[],用于整数运算。
- awk,awk 既可以用于整数运算,也可以用于小数运算。
- declare,定义变量值和属性,-i参数可以用于定义整形变量,做运算。
2 (()) 双小括号数值运算命令
双小括号 (()) 的作用是进行数值运算与数值比较,它的效率很高,用法灵活,是企业场景运维人员经常采用的运算操作符。
2.1 (()) 双小括号数值运算的基础语法
双小括号 (()) 的操作方法:
-
((i=i+1)),此种书写方法为运算后赋值法,即将i+1的运算结果赋值给变量i。
注意 :不能用
echo ((i=i+l))
输出表达式的值,可以用echo $((i=i+l))
输出其值。 -
**i=((i+1))∗∗,可以在'(())'前加'((i+1))**,可以在 `(())` 前加 `((i+1))∗∗,可以在'(())'前加'` 符,表示将表达式运算后赋值给i。
-
(( 8>7 && 5==5)),可以进行比较操作,还可以加入逻辑与和逻辑或,用于条件判断。
-
**echo ((2+1))∗∗,需要直接输出运算表达式的运算结果时,可以在'(())'前加'((2+1))**,需要直接输出运算表达式的运算结果时,可以在 `(())` 前加 `((2+1))∗∗,需要直接输出运算表达式的运算结果时,可以在'(())'前加'` 符。
2.2 (()) 双小括号数运算实践
**示例1:**简单的数值计算。
bash
[wsh@test ~ ✔]$ echo $((1+1))
2
[wsh@test ~ ✔]$ echo $((6*3))
18
[wsh@test ~ ✔]$ ((i=5))
[wsh@test ~ ✔]$ ((i=i*2))
[wsh@test ~ ✔]$ echo $i
10
**示例2:**复杂的数值计算。
bash
[wsh@test ~ ✔]$ ((a=1+2**3-4%3))
[wsh@test ~ ✔]$ echo $a
8
[wsh@test ~ ✔]$ b=$((a=1+2**3-4%3))
[wsh@test ~ ✔]$ echo $b
8
[wsh@test ~ ✔]$ echo $((a=1+2**3-4%3))
8
[wsh@test ~ ✔]$ a=$((100*(100+1)/2))
[wsh@test ~ ✔]$ echo $a
5050
[wsh@test ~ ✔]$ echo $((100*(100+1)/2))
5050
**示例3:**特殊运算符号
bash
[wsh@test ~ ✔]$ a=8;echo $((a+=1))
9
[wsh@test ~ ✔]$ echo $((a**2))
81
**示例4:**比较和判断
bash
[wsh@test ~ ✔]$ ((3<8))
[wsh@test ~ ✔]$ echo $?
0
[wsh@test ~ ✔]$ echo $((3<8))
1
[wsh@test ~ ✔]$ ((3>8))
[wsh@test ~ ✔]$ echo $?
1
[wsh@test ~ ✔]$ echo $((3>8))
0
[wsh@test ~ ✔]$ echo $((3==3))
1
[wsh@test ~ ✔]$ if ((8>7 && 5==5));then echo yes;fi
yes
**示例5:**变量前后使用--和++特殊运算符的表达式
bash
[wsh@test ~ ✔]$ a=10
[wsh@test ~ ✔]$ echo $((a++))
10
[wsh@test ~ ✔]$ echo $a
11
[wsh@test ~ ✔]$ echo $((--a))
10
[wsh@test ~ ✔]$ echo $a
10
总结:
- 执行
echo $((a++))
和echo $((a--))
命令输出整个表达式时,输出的值即为a
的值,表达式执行完毕后,会对a
进行++
、--
的运算 - 执行
echo $((++a))
和echo $((--a))
命令输出整个表达式时,会先对a
进行++
、--
的运算,然后再输出表达式的值,即为a运算后的值。
**示例6:**通过 (())运算后赋值给变量
bash
[wsh@test ~ ✔]$ num=99
[wsh@test ~ ✔]$ echo $((num+1))
100
[wsh@test ~ ✔]$ num=$((num+1))
[wsh@test ~ ✔]$ echo $num
100
3 let 命令
let运算命令的语法格式为:let 表达式
let表达式的功能等同于:((表达式))
示例:
bash
[wsh@test ~ ✔]$ i=2
[wsh@test ~ ✔]$ i=i+8
[wsh@test ~ ✔]$ echo $i
i+8
[wsh@test ~ ✔]$ i=2
[wsh@test ~ ✔]$ let i=i+8
[wsh@test ~ ✔]$ echo $i
10
4 expr 命令
expr(evaluate(求值)expressions(表达式))
命令既可以用于整数运算,也可以用于相关字符串长度、匹配等的运算处理。
4.1 expr 命令的基本用法示例
bash
[wsh@test ~ ✔]$ expr 2+2
2+2
[wsh@test ~ ✔]$ expr 2 + 2
4
[wsh@test ~ ✔]$ expr 2 * 2
expr: syntax error
[wsh@test ~ ✔]$ expr 2 \* 2
4
在使用 expr
时:
- 运算符及用于计算的数字左右都至少有一个空格,否则会报错。
- 使用乘号时,必须用反斜线屏蔽其特定含义,因为 Shell 可能会误解星号的含义。
4.2 expr 的企业级实战案例详解
**示例1:**判断一个变量值是否为整数
在 Shell 编程里,由于函数库很少,所以判断字符串是否为整数就不是一件很容易的事情。在这里,我们为读者介绍一种简单的可以判断一个字符串是否为整数的方法。
实现原理是,利用以 expr 做计算时变量或字符串必须是整数的规则,把一个变量或字符串和一个已知的整数(非0)相加,看命令返回的值是否为 0。如果为 0,就认为做加法的变量或字符串为整数,否则就不是整数。
bash
[wsh@test ~ ✔]$ i=5
[wsh@test ~ ✔]$ expr $i + 5
10
[wsh@test ~ ✔]$ echo $?
0
[wsh@test ~ ✔]$ i=a
[wsh@test ~ ✔]$ expr $i + 5
expr: non-integer argument
[wsh@test ~ ✔]$ echo $?
2
此外,用 expr match
功能进行整数判断时,可执行 man expr
命令获得帮助。
bash
# 变量值为非整数,返回值为0
[wsh@test ~ ✔]$ i=a
[wsh@test ~ ✔]$ expr match "$i" "^[1-9][0-9]*$"
0
# 变量值为整数,返回值为1
[wsh@test ~ ✔]$ i=5
[wsh@test ~ ✔]$ expr match "$i" "^[0-9][0-9]*$"
1
**示例2:**判断文件扩展命名是否符合要求
bash
[wsh@test ~ ✔]$ file=stu-202212.jpg
# 匹配扩展名,返回值为0,忽略输出结果
[wsh@test ~ ✔]$ expr "$file" : ".*\.jpg$"
14
[wsh@test ~ ✔]$ echo $?
0
# 未匹配扩展名,返回值为1,忽略输出结果
[wsh@test ~ ✔]$ expr "$file" : ".*\.pub$"
0
[wsh@test ~ ✔]$ echo $?
1
**示例3:**计算字符串的长度
bash
[wsh@test ~ ✔]$ str="abc123abc123"
[wsh@test ~ ✔]$ expr length "$str"
12
**示例4:**打印下面语句中字符数不大于6 的单词
bash
#!/bin/bash
strings="This is Linux World Welcome to our classroom"
for word in $strings
do
if [ $(expr length "$word") -le 6 ];then
echo $word
fi
done
执行结果如下:
bash
[wsh@test ~ ✔]$ bash str.sh
This
is
Linux
World
to
our
5 bc 命令
bc
是UNIX/Linux下的计算器,因此,除了可以作为计算器来使用,还可以作为命令行计算工具使用。
示例:
bash
[wsh@test ~ ✔]$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
1+3*4-6/3^3%4
13
scale=4
1/3
.3333
quit
[wsh@test ~ ✔]$ echo '1+3*4-6/3^3%4' | bc
13
[wsh@test ~ ✔]$ echo 'scale=4;1/3' | bc
.3333
6 awk 命令实现计算
利用awk进行运算的效果也很好,适合小数和整数,特别是命令行计算,尤其是小数,运算很精确,好用。
bash
[wsh@test ~ ✔]$ echo 7.7 3.8 | awk '{ print $1-$2 }'
3.9
[wsh@test ~ ✔]$ echo 358 113 | awk '{ print ($1-3)/$2 }'
3.14159
7 $[] 符号的运算
示例1:
bash
[wsh@test ~ ✔]$ echo $[1+1]
2
[wsh@test ~ ✔]$ echo $[4-2]
2
[wsh@test ~ ✔]$ echo $[2*2]
4
[wsh@test ~ ✔]$ echo $[4/2]
2
[wsh@test ~ ✔]$ echo $[5/2]
2
[wsh@test ~ ✔]$ echo $[5%2]
1
[wsh@test ~ ✔]$ count=3;echo $[(count+1)*3]
12
[wsh@test ~ ✔]$ count=3;echo $[ ++count + 3 ]
7
[wsh@test ~ ✔]$ count=3;echo $[ count++ + 3 ]
6
[wsh@test ~ ✔]$ count=3;echo $[ --count + 3 ]
5
[wsh@test ~ ✔]$ count=3;echo $[ count-- + 3 ]
6
**示例2:**通过一条命令计算输出 1+2+3+...+10
的表达式,并计算出结果,请使用bc命令计算。输出内容如1+2+3+4+5+6+7+8+9+10=55
。
bash
[wsh@test ~ ✔]$ echo $(seq -s + 10) | bc
[wsh@test ~ ✔]$ echo $(( $(echo {1..10} | tr ' ' '+' ) ))
[wsh@test ~ ✔]$ echo $[ $(echo {1..10} | tr ' ' '+' ) ]
Shell 脚本的条件测试
1 Shell 脚本的条件测试
1.1 条件测试方法综述
通常,在 bash 的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。
执行条件判断表达式后通常会返回"真"或"假",就像执行命令后的返回值为0,表示真;非0,表示假。
在 bash 编程里,条件测试常用的语法如下:
- 语法1:test <判断表达式>,test命令和"<判断表达式>"之间至少有一个空格。
- 语法2:[ <判断表达式> ] ,和test命令的用法相同,这是推荐方法。
[]
的边界和内容之间至少有一个空格。 - 语法3:[[ <判断表达式> ]] ,是比test和[]更新的语法格式。
[[]]
的边界和内容之间至少有一个空格。 - 语法4:(( <判断表达式> )) ,一般用于 if 语句。
(())
(双小括号)两端不需要有空格。 - 语法5:comand,命令的返回值确定表达式的真值或假值。
有几个注意事项需要说明一下:
- 语法1 中的
test命令
和语法2 中的[]
是等价的,语法3
中的[[]]
为扩展的test命令,语法4 中的(())
常用于计算。建议使用相对友好的语法2 ,即中括号[]
的语法格式。 - 在
[[ ]]
(双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。 &&、||、>、<
等操作符可以应用于[[]]
中,但不能应用于[]
中,在[]
中一般用-a、-o、-gt(
用于整数)、-lt
(用于整数)代替上述操作符。- 对于整数的关系运算,也可以使用 Shell 的算术运算符
(())
。
1.2 test 条件测试的简单语法及示例
test条件测试的语法格式为: test <判断表达式>
bash
[wsh@test ~ ✔]$ help test
test: test [expr]
Evaluate conditional expression.
Exits with a status of 0 (true) or 1 (false) depending on
the evaluation of EXPR. Expressions may be unary or binary. Unary
expressions are often used to examine the status of a file. There
are string operators and numeric comparison operators as well.
The behavior of test depends on the number of arguments. Read the
bash manual page for the complete specification.
File operators:
-a FILE True if file exists.
-b FILE True if file is block special.
-c FILE True if file is character special.
-d FILE True if file is a directory.
-e FILE True if file exists.
-f FILE True if file exists and is a regular file.
-g FILE True if file is set-group-id.
-h FILE True if file is a symbolic link.
-L FILE True if file is a symbolic link.
-k FILE True if file has its `sticky' bit set.
-p FILE True if file is a named pipe.
-r FILE True if file is readable by you.
-s FILE True if file exists and is not empty.
-S FILE True if file is a socket.
-t FD True if FD is opened on a terminal.
-u FILE True if the file is set-user-id.
-w FILE True if the file is writable by you.
-x FILE True if the file is executable by you.
-O FILE True if the file is effectively owned by you.
-G FILE True if the file is effectively owned by your group.
-N FILE True if the file has been modified since it was last read.
FILE1 -nt FILE2 True if file1 is newer than file2 (according to
modification date).
FILE1 -ot FILE2 True if file1 is older than file2.
FILE1 -ef FILE2 True if file1 is a hard link to file2.
String operators:
-z STRING True if string is empty.
-n STRING
STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.
Other operators:
-o OPTION True if the shell option OPTION is enabled.
-v VAR True if the shell variable VAR is set
! EXPR True if expr is false.
EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
Arithmetic binary operators return true if ARG1 is equal, not-equal,
less-than, less-than-or-equal, greater-than, or greater-than-or-equal
than ARG2.
Exit Status:
Returns success if EXPR evaluates to true; fails if EXPR evaluates to
false or an invalid argument is given.
**示例1:**判断文件是否存在
bash
[wsh@test ~ ✔]$ test -f file && echo true || echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&
是逻辑与,||
是逻辑或。test的-f
参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||
后面的命令是test命令执行失败之后(为假)所执行的命令。
test命令判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
bash
[wsh@test ~ ✔]$ test -f file && echo true
[wsh@test ~ ✔]$ test -f file || echo false
另外,逻辑操作符&&
和||
的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
示例2: -z
选项判断变量值是否为空,如果为空,则为真值,反之为假值。
bash
[wsh@test ~ ✔]$ unset string
[wsh@test ~ ✔]$ test -z "$string" && echo null string || echo $string
null string
[wsh@test ~ ✔]$ string="hello world"
[wsh@test ~ ✔]$ test -z "$string" && echo null string || echo $string
hello world
结论:test命令测试的功能很强大,但是和[]
、[[]]
的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其他的语法,能读懂别人写的脚本就可以了。
1.3 [](中括号)条件测试语法及示例
[]
条件测试的语法格式为:[ < 判断表达式> ]
注意:中括号内部的两端要有空格,[]
和test等价,即test的所有判断选项都可以直接在[]
里使用。
**示例1:**判断文件是否存在
bash
[wsh@test ~ ✔]$ [ -f file ] && echo true || echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&
是逻辑与,||
是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&
后面的命令,而||
后面的命令是test命令执行失败之后(为假)所执行的命令。
\]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个\&\&或\|\|)来测试,示例如下。 ```bash [wsh@test ~ ✔]$ [ -f file ] && echo true [wsh@test ~ ✔]$ [ -f file ] || echo false ``` > 逻辑操作符 `&&` 和 `||` 的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。 ##### 1.4 \[\[\]\](双中括号)条件测试语法及示例 `[[]]`条件测试的语法格式为: `[[ < 判断表达式> ]]`。 注意:双中括号里的两端也要有空格。 \*\*示例1:\*\*判断文件是否存在 ```bash [wsh@test ~ ✔]$ [[ -f file ]] && echo true || echo false ``` 该语句表示如果file文件存在,则输出true,否则输出false。这里的\&\&是逻辑与,\|\|是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行\&\&后面的命令,而\|\|后面的命令是test命令执行失败之后(为假)所执行的命令。 \[\]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个\&\&或\|\|)来测试,示例如下。 ```bash [wsh@test ~ ✔]$ [[ -f file ]] && echo true [wsh@test ~ ✔]$ [[ -f file ]] || echo false ``` > 逻辑操作符 `&&` 和 `||` 的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。 #### 2 文件判断表达式 ##### 2.1 文件判断表达式的用法 文件测试常用选项: * **-d 文件** ,d的全拼为**directory**文件存在且为目录则为真,即判断表达式成立。 * **-f 文件** ,f的全拼为**file**文件存在且为普通文件则为真,即判断表达式成立。 * **-e 文件** ,e的全拼为**exist**文件存在则为真,即判断表达式成立。区别于"-f",-e不辨别是目录还是文件。 * **-r 文件** ,r的全拼为**read**文件存在且可读则为真,即判断表达式成立。 * **-s 文件** ,s的全拼为size文件存在且文件**size不为0**则为真,即判断表达式成立。 * **-w 文件** ,w的全拼为**write**文件存在且可写则为真,即判断表达式成立。 * **-x 文件** ,x的全拼为**executable**文件存在且可执行则为真,即判断表达式成立。 * **-L 文件** ,L的全拼为**link**文件存在且为链接文件则为真,即判断表达式成立。 * **f1 -nt f2** ,nt的全拼为**newer than**,文件fl比文件f2新则为真,即判断表达式成立。根据文件的修改时间来计算。 * **f1 -ot f2**,ot的全拼为older than,文件fl比文件f2旧则为真,即判断表达式成立。根据文件的修改时间来计算。 ##### 2.2 文件判断表达式举例 ```bash [wsh@test ~ ✔]$ data_path=/tmp/wsh [wsh@test ~ ✔]$ mkdir ${data_path} [wsh@test ~ ✔]$ [ -d ${data_path} ] && echo ${data_path} is exist ${data_path} is exist [wsh@test ~ ✔]$ rmdir ${data_path} [wsh@test ~ ✔]$ [ -d ${data_path} ] || echo ${data_path} is not exist /tmp/wsh is not exist [wsh@test ~ ✔]$ [ -r /etc/shadow ] && cat /etc/shadow || echo hello hello [wsh@test ~ ✔]$ [ -w ~/.bashrc ] && echo "alias pa='ps axu'" >> ~/.bashrc [wsh@test ~ ✔]$ tail -1 ~/.bashrc alias pa='ps axu' ``` ##### 2.3 特殊条件判断表达式案例 以下写法适用于所有的条件判断表达式,是工作中比较常用的替代if语句的方法。判断条件判断表达式的条件成立或不成立后,还需要继续**执行多条命令语句**的语法。 例如,当条件1成立时,同时执行命令1、命令2、命令3。 ```bash # 格式1 [ 条件1 ] && { 命令1 命令2 命令3 } # 格式2 [[ 条件1 ]] && { 命令1 命令2 命令3 } # 格式3 test 条件1 && { 命令1 命令2 命令3 } ``` **示例:** ```bash [wsh@test ~ ✔]$ [ -w ~/.bashrc ] && { echo "alias sa='ssh root@servera'" >> ~/.bashrc echo "alias sb='ssh root@serverb'" >> ~/.bashrc echo "alias sc='ssh root@serverc'" >> ~/.bashrc } [wsh@test ~ ✔]$ tail -3 ~/.bashrc alias sa='ssh root@servera' alias sb='ssh root@serverb' alias sc='ssh root@serverc' # 一行写法 [wsh@test ~ ✔]$ [ -r /etc/passwd ] && { echo ha; echo ha; echo ha; } ha ha ha ``` #### 3 字符串判断表达式 ##### 3.1 字符串测试操作符 字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。 常用字符串测试操作符: * **-n "字符串"**,若字符串的长度不为0,则为真,即判断表达式成立,n可以理解为no zero。 * **-z "字符串"**,若字符串的长度为0,则为真,即判断表达式成立,z可以理解为zero的缩写。 * **"串1" = "串2"**,若字符串1等于字符串2,则为真,即判断表达式成立,可使用"=="代替"="。 * **"串1" != "串2"**,若字符串1不等于字符串2,则为真,即判断表达式成立,但不能用"!=="代替"!="。 以下是针对字符串测试操作符的提示: * 对于字符串的测试,**一定要将字符串加双引号之后再进行比较** ,如 `[ -n "$myvar" ]`,特别是使用`[]` 的场景。 * 比较符号(例如=和!=)的两端一定要有空格。 **示例1:-z 选项**,判断变量值是否为空,如果为空,则为真值,反之为假值。 ```bash [wsh@test ~ ✔]$ unset string [wsh@test ~ ✔]$ [ -z "$string" ] && echo null string || echo $string null string [wsh@test ~ ✔]$ string="hello world" [wsh@test ~ ✔]$ [ -z "$string" ] && echo null string || echo $string hello world ``` **示例2:-n 选项**,判断变量值是否为非空,如果为非空,则为真值,反之为假值。 ```bash [wsh@test ~ ✔]$ unset string [wsh@test ~ ✔]$ [ -n "$string" ] && echo $string || echo null string null string [wsh@test ~ ✔]$ string="hello world" [wsh@test ~ ✔]$ [ -n "$string" ] && echo $string || echo null string hello world ``` \*\*示例3:\*\*判断是否相对 ```bash [wsh@test ~ ✔]$ [ "$string" = "hi" ] && echo hi hi [wsh@test ~ ✔]$ [ "$string" = "ha" ] && echo ha [wsh@test ~ ✔]$ [ "$string" != "hi" ] && echo no hi [wsh@test ~ ✔]$ [ "$string" != "ha" ] && echo ha ha ``` ##### 3.2 字符串测试生产案例 **示例:** /etc/init.d/network 部分内容 ```bash [wsh@test ~ ✔]$ sed -n '30,31p' /etc/init.d/network # Check that networking is up. [ "${NETWORKING}" = "no" ] && exit 6 ``` ##### 3.3 \[\[\]\]中=\~操作符 **语法:** `[[ "$str" =~ pattern ]]` \*\*作用:\*\*使用 `=~` 操作符时,其右边的字符串被认为是一个扩展正则表达式。扩展之后跟左边字符串进行比较,看左边字符串是否包含右边模式,也就是判断右边的模式是否为左边字符串的子字符串,而不是判断右边的模式是否完全等于左边字符串。 **`=~` 操作符右边的字符串不能使用引号括起来**,无论是双引号、还是单引号,否则表示匹配这个字符串自身的内容,不再解析成正则表达式。 示例: ```bash # 判断变量值是否包涵数字 [wsh@test ~ ✔]$ str=123abc [wsh@test ~ ✔]$ [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num str is a string with num [wsh@test ~ ✔]$ str=abcdef [wsh@test ~ ✔]$ [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num str is a string without any num # 判断变量值是否只包涵数字 [wsh@test ~ ✔]$ str=123;[[ "$str" =~ ^[0-9]+$ ]] && echo str is a num || echo str is not a num str is a num # 加引号对比 [wsh@test ~ ✔]$ str=123;[[ "$str" =~ [0-9]+ ]] && echo true || echo false true [wsh@test ~ ✔]$ str=123;[[ "$str" =~ "[0-9]+" ]] && echo true||echo false false ``` #### 4 整数二元比较操作符 ##### 4.1 整数二元比较操作符介绍 整数二元比较操作符使用说明如下: | \[\]和test中比较符号 | (())和\[\[\]\]中比较符号 | 说明 | |:--------------:|:------------------:|:----------------------:| | -eq | ==或= | 相等,全拼为 equal | | -ne | != | 不相等,全拼为 not equal | | -gt | \> | 大于,全拼为 greater than | | -ge | \>= | 大于等于,全拼为 greater equal | | -It | \< | 小于,全拼为less than | | -le | \<= | 小于等于,全拼为 less equal | 以下是针对上述符号的特别说明: * `=` 和 `!=` 也可在\[\]中做比较使用,但在\[\]中使用包含 `>` 和 `<` 的符号时,需要用反斜线转义,有时不转义虽然语法不会报错,但是结果可能会不对。 * 也可以在\[\[\]\]中使用包含`-gt` 和 `-lt` 的符号,但是不建议这样使用。 * 比较符号两端也要有空格。 **示例1:** ```bash [wsh@test ~ ✔]$ [ 2 -lt 3 ] && echo true || echo false true [wsh@test ~ ✔]$ [ 2 -gt 3 ] && echo true || echo false false [wsh@test ~ ✔]$ [ 2 -le 3 ] && echo true || echo false true [wsh@test ~ ✔]$ [ 4 -ge 3 ] && echo true || echo false true ``` **示例2:** 错误的示例 ```bash [wsh@test ~ ✔]$ [ 2 > 1 ] && echo true true # 事实是2<3 [wsh@test ~ ✔]$ [ 2 > 3 ] && echo true true # 转义>符号 [wsh@test ~ ✔]$ [ 2 \> 3 ] && echo true || echo false false ``` **\[\]、\[\[\]\]、(())用法的小结:** * 整数加双引号的比较是对的。 * `[[]]`中用类似-eq等的写法是对的,\[\[\]\]中用类似\>、\<的写法也可能不对,有可能会只比较第一位,逻辑结果不对。 * `[]`中用类似\>、\<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较。 * `(())`中不能使用类似-eq等的写法,可以使用类似\>、\<的写法。 ##### 4.2 整数变量测试实践示例 **示例1:** ```bash [wsh@test ~ ✔]$ a=98;b=99 [wsh@test ~ ✔]$ [ $a -eq $b ] && echo true || echo false false [wsh@test ~ ✔]$ [ $a -ne $b ] && echo true || echo false true [wsh@test ~ ✔]$ [ $a -gt $b ] && echo true || echo false false [wsh@test ~ ✔]$ [ $a -lt $b ] && echo true || echo false true ``` **示例2:** ```bash [wsh@test ~ ✔]$ a=98;b=99 [wsh@test ~ ✔]$ [[ $a = $b ]] && echo true || echo false false [wsh@test ~ ✔]$ [[ $a != $b ]] && echo true || echo false true [wsh@test ~ ✔]$ (( $a >= $b )) && echo true || echo false false [wsh@test ~ ✔]$ (( $a <= $b )) && echo true || echo false true ``` #### 5 逻辑操作符 ##### 5.1 逻辑操作符介绍 **表达式中**逻辑操作符使用说明如下: | \[\]和test中逻辑操作符 | \[\[\]\]和(())中逻辑操作符 | 说明 | |:---------------:|:-------------------:|:--------------------| | -a | \&\& | and,与,两端都为真,则结果为真 | | -o | II | or,或 ,两端有一个为真,则结果为真 | | ! | ! | not,非,两端相反,则结果为真 | 对于上述操作符,有如下提示: * 逻辑操作符前后的表达式是否成立,一般用真假来表示。 * "!"的中文意思是反,即与一个逻辑值相反的逻辑值。 * -a的中文意思是"与"(and或\&\&),前后两个逻辑值都为"真",综合返回值才为"真",反之为"假"。 * -o的中文意思是"或"(or或\|\|),前后两个逻辑值只要有一个为"真",返回值就为"真"。 **表达式外:**\&\&或\|\|可用于连接两个含\[\]、test或\[\[\]\]的表达式: * 使用\&\&表达式时: * 当左边为真,右边表达式才会执行。 * 当左边为假,右边表达式不会执行。 * 使用\|\|的表达式时: * 当左边为真,右边表达式不会执行。 * 当左边为假,右边表达式才会执行。 ##### 5.2 逻辑操作符实践示例 ```bash # 判断表达式内部:逻辑与 [wsh@test ~ ✔]$ file=/etc/passwd [wsh@test ~ ✔]$ [ -f $file -a -r $file ] && head -1 $file root:x:0:0:root:/root:/bin/bash [wsh@test ~ ✔]$ [[ -f $file && -r $file ]] && head -1 $file root:x:0:0:root:/root:/bin/bash # 判断表达式内部:逻辑或 [wsh@test ~ ✔]$ num=10 [wsh@test ~ ✔]$ [ $num -lt 20 -o $num -gt 1 ] && echo $num is between 1 and 20. 10 is between 1 and 20. [wsh@test ~ ✔]$ [[ $num -lt 20 || $num -gt 1 ]] && echo $num is between 1 and 20. is between 1 and 20. # 判断表达式内部:逻辑非 [wsh@test ~ ✔]$ [ ! 6 -eq 5 ] && echo 6!=5 6!=5 [wsh@test ~ ✔]$ [[ ! 6 -eq 5 ]] && echo 6!=5 6!=5 ``` ##### 5.3 逻辑操作符企业案例 ```bash [wsh@test ~ ✔]$ data_path=/tmp/wsh [wsh@test ~ ✔]$ mkdir ${data_path} [wsh@test ~ ✔]$ [ -d ${data_path} ] && echo ${data_path} is exist /tmp/wsh is exist [wsh@test ~ ✔]$ rmdir ${data_path} [wsh@test ~ ✔]$ [ -d ${data_path} ] && echo ${data_path} is not exist [wsh@test ~ ✔]$ [ -d ${data_path} ] || mkdir ${data_path} [wsh@test ~ ✔]$ ls -d ${data_path} /tmp/wsh ``` #### 6 判断表达式 test、\[\]、\[\[\]\]、(())的区别总结 | 判断表达式符号 | \[\] | test | \[\[\]\] | (()) | |-----------|:-----------------------:|:-----------------------:|:-----------------------:|:------------------:| | 边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 | | 逻辑操作符 | !、-a、-o | !、 -a、 -o | !、\&\&、\|\| | !、\&\&、\|\| | | 整数比较操作符 | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | =、!=、\>、\>=、\<、\<= | | 字符串比较操作符 | =、== 、!= | =、== 、!= | =、== 、!= | == 、!= | | 是否支持通配符匹配 | 不支持 | 不支持 | 支持(=\~) | 不支持 | ### if 条件语句的知识与实践 #### 1 if 条件语句 if条件语句是Linux运维人员在实际生产工作中使用得最频繁也是最重要的语句,因此,请务必重视if条件语句的知识,并牢固掌握。 ##### 1.1 if 条件语句的语法 if条件语句,其语义类似于汉语里的"如果...那么"。 ###### 1. 单分支结构 第一种语法: ```bash if <条件表达式> then 指令 fi ``` 第二种语法: ```bash if <条件表达式>;then 指令 fi ``` 上文的"\<条件表达式\>"部分可以是test、口、\[\[\]\]、(())等条件表达式,甚至可以直接使用命令作为条件表达式。每个if条件语句都以if开头,并带有then,最后以fi结尾。 第二种语法中的分号相当于命令换行,上面的两种语法含义是相同的,读者可根据习惯自行选择。 > 本书主要使用第二种语法格式。 在所有编程语言里,if条件语句几乎是最简单的语句格式,且用途最广。当if后面的\<条件 表达式\>成立时(真),就会执行then后面的指令或语句;否则,就会忽略then后面的指令或语句, 转而执行fi下面的程序。 if单分支语句执行流程逻辑图如下:  条件语句还可以嵌套(即if条件语句里面还有if条件语句),注意每个if条件语句中都要有一个与之对应的fi(if反过来写),每个if和它下面最近的fi成对搭配,语法示例如下: ```bash if <条件表达式>;then if <条件表达式>;then 指令 fi fi ``` 提示:通常在书写Shell条件语句时,要让成对的条件语句关键字的缩进相对应,以便于阅读浏览。 前文曾讲解过的文件条件表达式: ```bash [ ! -d /tmp/wsh ] && mkdir /tmp/wsh ``` 等价于下面的if条件语句: ```bash if [ ! -d /tmp/wsh ];then mkdir /tmp/wsh fi ``` > 记忆小技巧:女孩对男孩说。 > > ```bash > 如果 你有房;那么 > 我就嫁给你 > 果如 > ``` ###### 2. 双分支结构 if条件语句的双分支结构主体则为:"如果...,那么...,否则..."。 if条件语句的双分支结构语法为: ```bash if <条件表达式>;then 指令集1 else 指令集2 fi ``` if 双分支语句执行流程逻辑图如下:  前文的文件测试条件表达式 ```bash [ -d /tmp/wsh ] && echo /tmp/wsh is exist || mkdir /tmp/wsh ``` 就相当于下面的双分支的if条件语句: ```bash if [ -d /tmp/wsh ];then echo /tmp/wsh is exist else mkdir /tmp/wsh fi ``` > 记忆小技巧:女孩对男孩说。 > > ```bash > 如果 你有房;那么 > 我就嫁给你 > 否则 > 我再考虑考虑 > 果如 > ``` ###### 3. 多分支结构 if条件语句多分支结构的主体为:"如果...,那么...,否则如果...,那么,否则如果...,那么...,否则..."。 if条件语句多分支语法为: ```bash if <条件表达式1>;then 指令1 elif <条件表达式2>;then 指令2 else 指令3 fi ``` 多个elif ```bash if <条件表达式1>;then 指令 elif <条件表达式2>;then 指令 elif <条件表达式3>;then 指令 else 指令 fi ``` 提示: 1. 注意多分支elif的写法,每个elif都要带有then。 2. 最后结尾的else后面没有then。 if多分支语句执行流程对应的逻辑图如下:  > 记忆小技巧:女孩对男孩说。 > > ```bash > 如果 你有房;那么 > 我就嫁给你 > 或者 你有钱;那么 > 我也可以嫁给你 > 否则 > 我再考虑考虑 > 果如 > ``` ##### 1.2 if 条件语句多种条件表达式语法 if条件语句(包括双多分支if)的"\<条件表达式\>"部分可以是test、\[\]、\[\[\]\]、(())等条件表达式,甚至还可以直接使用命令作为条件表达式,具体的语法如下。 ###### 1. test条件表达式 ```bash if test 表达式;then 指令 fi ``` ###### 2. \[\]条件表达式 ```bash if [ 字符串 或 算术表达式 ];then 指令 fi ``` ###### 3. \[\[\]\]条件表达式 ```bash if [[ 字符串 或 算术表达式 ]];then 指令 fi ``` ###### 4. (())条件表达式 ```bash if ((算术表达式));then 指令 fi ``` ###### 5. 命令表达式 ```bash if 命令;then 指令 fi ``` 说明:以上表达式除了语法不同之外,具体的应用是一致的,实际工作场景中,读者只需选择一种适合自己习惯的表达式就好。 ##### 1.3 if 条件语句实践 **示例1**:检测sshd服务是否运行,如果未运行则启动sshd服务。 ```bash #!/bin/bash systemctl is-active sshd &>/dev/null if [ $? -ne 0 ];then echo "sshd is not running, I'll start sshd." systemctl start sshd fi ``` **示例2**:检测sshd服务是否运行,如果未运行则启动sshd服务;如果运行,输出 "Running"。 ```bash #!/bin/bash systemctl is-active sshd &>/dev/null if [ $? -ne 0 ];then echo "sshd is not running." echo -n "Starting sshd ... ..." systemctl start sshd && echo DONE else echo "sshd is running" fi ``` **示例3**:通过传参控制sshd服务。 ```bash #!/bin/bash if [ "$1" == "start" ];then systemctl start sshd elif [ "$1" == "stop" ];then systemctl stop sshd elif [ "$1" == "status" ];then systemctl status sshd elif [ "$1" == "restart" ];then systemctl restart sshd else echo "Usage: $0 start|stop|status|restart " fi ``` 或者 ```bash #!/bin/bash if [ "$1" = "start" -o "$1" = "stop" -o "$1" = "status" -o "$1" = "restart" ];then systemctl $1 sshd else echo "Usage: $0 start|stop|status|restart" fi ``` **示例4**:任意给三个整数,对三个数进行从大到小排序并输出。 ```bash #!/bin/bash a=10 b=20 c=30 # 如果a小于b,交换值,此时a大b小 if [ $a -lt $b ];then num=$b;b=$a;a=$num fi # 比较后两个值大小并交换,此时c值最小 if [ $b -lt $c ];then num=$c;c=$b;b=$num; fi # 比较前两个值大小并交换,此时a值最大 if [ $a -lt $b ];then num=$b;b=$a;a=$num; fi echo "$a>$b>$c" ``` **示例5**:每3分钟检查一次系统可用内存,如果空闲内存低于100M时给root用户发邮件。 对于开发程序而言,一般来说应该遵循下面的3步法则。 1. 分析需求 明白开发需求,是完成程序的大前提,因此,分析需求至关重要,一切不以需求为主的程序开发,都是不倡导的! 2. 设计思路 设计思路就是根据需求,把需求进行拆解,分模块逐步实现,例如本题可以分为如下几步: 1. 获取当前系统剩余内存的值(先在命令行实现)。 2. 配置邮件报警(可采用第三方邮件服务器)。 3. 判断取到的值是否小于100MB,如果小于100MB,就报警(采用if语句)。 4. 编码实现Shell脚本。 5. 加入crond定时任务,每三分钟检查一次。 3. 编码实现 编码实现就是具体的编码及调试过程,工作中很可能需要先在测试环境下调试,调试好了,再发布到生产环境中。 本例的最终实现过程如下: 1. 获取可用内存大小。 ```bash [wsh@test ~ ✔]$ free -m total used free shared buff/cache available Mem: 3931 327 3419 11 184 3388 Swap: 3967 0 3967 [wsh@test ~ ✔]$ free -m | awk 'NR==2 { print $4}' 3419 ``` 2. 发邮件的客户端常见的有mail或mutt;服务端有sendmail服务(CentOS5下默认的)、postfix服务(CentOS6下默认的)。这里不使用本地的邮件服务。 3. 编写Shell脚本:monitor_mem.sh。 ```bash #!/bin/bash FreeMem=$(free -m | awk 'NR==2 { print $4}') if [ $FreeMem -lt 100 ];then echo "Mem is lower than 100M" | mail -s "FreeMem is ${FreeMem}M" root@localhost fi ``` 4. 加入到计划任务 ```bash [wsh@test ~ ✔]$ chmod +x monitor_mem.sh [wsh@test ~ ✔]$ crontab -e no crontab for wsh - using an empty one crontab: installing new crontab [wsh@test ~ ✔]$ crontab -l */3 * * * * /home/wsh/monitor_mem.sh ``` #### 2 if 条件语句企业案例精讲 ##### 2.1 监控 Web 和数据库 用if条件语句针对Nginx Web服务或MySQL数据库服务是否正常进行检测,如果服务未启动,则启动相应的服务。 这是企业级运维实战综合题,需要读者对NginxWeb服务或MySQL数据库服务很熟悉才行,本例同样适用于其他的Web服务和数据库服务。 大家还记得前面说过的开发程序前的三部曲吧?这里就采用这种方式来解答此题。 ###### (1) 分析问题 监控Web服务和MySQL数据库服务是否异常的方法: * **端口监控** 1. 在服务器本地监控服务端口的常见命令有 netstat、ss、lsof 2. 从远端监控服务器本地端口的命令有 telnet、nmap、nc * **监控服务进程或进程数**,此方法适合本地服务器,注意,过滤的是进程的名字。命令为: ps -ef | grep nginx | wc -1 ps -ef | grep mysql | wc -1 * **客户端模拟用户访问**: * 使用wget或curl命令进行测试(如果监测数据库,则需要转为通过Web服务器去访问数据库),并对测试结果做三种判断: * 利用返回值($?)进行判断 * 获取特殊字符串以进行判断(需要事先开发好程序) * 根据HTTP响应header的情况进行判断 * 登录MySQL数据库判断 * 通过MySQL客户端连接数据库,根据返回值或返回内容判断。例如: ```bash mysql -u root -pwsh -e "select version();" &>/dev/null;echo$? ``` 此外,对端口进程等进行判断时,尽量先通过grep过滤端口和进程特殊标记字符串,然后结合wc将过滤到的结果转成行数再比较,这样相对简单有效,且经过wc-1命令处理之后的结果一定是数字,这样再进行判断就会比较简便。如果单纯地根据具体的列取具体的值判断会很麻烦,如果确实想采用取值判断的方法,那就尽量用字符串比较的语法。 **提示:** 掌握技术思想比解决问题本身更重要。 ###### (2) 监测 MySQL 数据库异常 1. MySQL 数据库环境准备 ```bash [wsh@test ~ ✔]$ sudo yum install -y mariadb-server [wsh@test ~ ✔]$ sudo systemctl enable mariadb --now [wsh@test ~ ✔]$ sudo ss -lnt|grep 3306 LISTEN 0 50 *:3306 *:* # 关闭防火墙 [wsh@test ~ ✔]$ sudo systemctl disable firewalld.service --now ``` 2. 通过命令行检测数据库服务是否正常,只有先确定命令行是正确的,才能确保将它放到脚本里也是正确的。 * 首先采用端口监控的方式。 **在服务器本地**监控端口的命令有netstat、ss、lsof,这里使用ss: ```bash [wsh@test ~ ✔]$ ss -lnt|grep ':3306'|wc -l 1 ``` **从远端监控服务器**监控本地端口的命令有telnet、nmap、nc,这里使用nmap: ```bash [wsh@test ~ ✔]$ sudo yum install -y nmap nc telnet [wsh@test ~ ✔]$ nmap 127.0.0.1 -p 3306|grep open 3306/tcp open mysql [wsh@test ~ ✔]$ nmap 127.0.0.1 -p 3306|grep open|wc -l 1 ``` 本例为了统一IP地址,因此使用的都是同一个IP,即127.0.0.1,在实际工作中,应该用自己服务器的IP来替代。 * 以下是在客户端模拟用户访问的方式进行监控。 使用mysql连接数据库,根据执行命令的返回值判断成功与否。 ```bash [wsh@test ~ ✔]$ mysql -uroot -h 127.0.0.1 -e 'select version();' &>/dev/null [wsh@test ~ ✔]$ echo $? 0 ``` 3. 开发监控MySQL数据库的脚本。 ```bash #!/bin/bash if ss -lnt|grep -q ':3306';then echo "MySQL is Running." else echo "MySQL is Not Running." fi ``` ###### (3) 监测 Web 服务器异常 1. Web 服务器准备 ```bash [wsh@test ~ ✔]$ sudo yum install -y httpd [wsh@test ~ ✔]$ sudo systemctl enable httpd --now [wsh@test ~ ✔]$ sudo ss -lnt|grep ':80 ' LISTEN 0 128 [::]:80 [::]:* # 关闭防火墙 [wsh@test ~ ✔]$ sudo systemctl disable firewalld.service --now ``` 2. 通过命令行检测数据库服务是否正常,只有先确定命令行是正确的,才能确保将它放到脚本里也是正确的。 * 首先采用端口监控的方式。 **在服务器本地**监控端口的命令有netstat、ss、lsof,这里使用ss: ```bash [wsh@test ~ ✔]$ ss -lnt|grep ':80'|wc -l 1 ``` **从远端监控服务器**监控本地端口的命令有telnet、nmap、nc,这里使用nmap: ```bash [wsh@test ~ ✔]$ sudo yum install -y nmap nc telnet [wsh@test ~ ✔]$ nmap 127.0.0.1 -p 80|grep open 3306/tcp open mysql [wsh@test ~ ✔]$ nmap 127.0.0.1 -p 80|grep open|wc -l 1 ``` 本例为了统一IP地址,因此使用的都是同一个IP,即127.0.0.1,在实际工作中,应该用自己服务器的IP来替代。 * 以下是在客户端模拟用户访问的方式进行监控。 使用wget或curl命令访问URL地址来测试。根据执行命令的返回值判断成功与否,本例的URL使用了网上的地址,在实际工作中应使用开发人员提供给我们的访问数据库的程序地址。 ```bash # 方式1 [wsh@test ~ ✔]$ wget --timeout=10 --tries=2 www.redhat.com 2>/dev/null [wsh@test ~ ✔]$ echo $? 0 # 方式2 [wsh@test ~ ✔]$ wget --timeout=10 --tries=2 www.redhat.com -q [wsh@test ~ ✔]$ echo $? 0 # 方式3 [wsh@test ~ ✔]$ curl -s www.redhat.com [wsh@test ~ ✔]$ echo $? 0 ``` 3. 开发监控Web服务器的脚本 ```bash #!/bin/bash if wget --timeout=10 --tries=2 www.redhat.com &>/dev/null;then echo "Apache is Running." else echo "Apache is Not Running." fi ``` ##### 2.2 比较大小的经典拓展案例 使用if条件语句比较两个整数的大小。使用传参方法时,需要对传参个数及传入的参数是否为整数进行判断。 **示例1:** ```bash #!/bin/bash # 判断参数个数 if [ $# -ne 2 ];then echo "USAGE: $0 numl num2" exit 1 fi # 赋值 a=$1 b=$2 # 判断参数是否为整数 expr $a + 1 &>/dev/null RETVAL1=$? expr $b + 1 &>/dev/null RETVAL2=$? if [ $RETVAL1 -ne 0 -o $RETVAL2 -ne 0 ];then echo "please provide two int number" exit 2 fi # 比较 if [ $a -lt $b ];then echo "$a<$b" elif [ $a -eq $b ];then echo "$a=$b" else echo "$a>$b" fi ``` **示例2:** ```bash #!/bin/sh read -p "Pls input two num:" a b expr $a + 0 & >/dev/null RETVAL1=$? expr $b + 0 & >/dev/null RETVAL2=$? if [ -z "$a" ] || [ -z "$b" ];then echo "Pls input two num again. exit 1 elif test $RETVAL1 -ne 0 -o $RETVAL2 -ne 0;then echo "Pls input two int num again. exit 2 elif [ $a -lt $b ];then echo "$a < $b" elif [ $a -eq $b ];then echo "$a = $b" else echo "$a > $b" fi exit 0 ``` ##### 2.3 判断字符串是否为数字的多种思路 **示例1:** 删除字符串中的所有数字,看字符串的长度是否为0 , 如果不为0,则说明不是整数。 **sed替换:** ```bash [wsh@test ~ ✔]$ [ -n "$(echo wsh123|sed 's/[0-9]//g')" ] && echo char || echo int char [wsh@test ~ ✔]$ [ -n "$(echo 123|sed 's/[0-9]//g')" ] && echo char || echo int int [wsh@test ~ ✔]$ [ -z "$(echo 123|sed 's/[0-9]//g')" ] && echo int || echo char int [wsh@test ~ ✔]$ [ -z "$(echo wsh123|sed 's/[0-9]//g')" ] && echo int || echo char char ``` **使用变量的子串替换** : ```bash [wsh@test ~ ✔]$ string=wsh123 [wsh@test ~ ✔]$ [ -n "${string//[0-9]/}" ] && echo char || echo int char [wsh@test ~ ✔]$ string=123 [wsh@test ~ ✔]$ [ -n "${string//[0-9]/}" ] && echo char || echo int int ``` **使用tr替换:** ```bash [wsh@test ~ ✔]$ string=123 [wsh@test ~ ✔]$ [ -z "$(echo $string | tr -d 0-9)" ] && echo int || echo char int [wsh@test ~ ✔]$ string=abc123 [wsh@test ~ ✔]$ [ -z "$(echo $string | tr -d 0-9)" ] && echo int || echo char char ``` **示例2:** 如果num的长度不为0,并且把num中的非数字部分删除,然后再看结果是不是等于num本身,如果两者都成立,则num就是数字。 ```bash [wsh@test ~ ✔]$ num=abc123 [wsh@test ~ ✔]$ [ -n "$num" -a "$num" = "${num//[^0-9]/}" ] && echo int || echo char char [wsh@test ~ ✔]$ num=123 [wsh@test ~ ✔]$ [ -n "$num" -a "$num" = "${num//[^0-9]/}" ] && echo int || echo char int ``` **示例3:** 通过 expr 计算判断 ```bash [wsh@test ~ ✔]$ expr abc + 1 &>/dev/null [wsh@test ~ ✔]$ echo $? 2 [wsh@test ~ ✔]$ expr 1 + 1 &>/dev/null [wsh@test ~ ✔]$ echo $? 0 ``` **示例4:** 通过"=\~"符号判断 ```bash [wsh@test ~ ✔]$ [[ 123 =~ ^[0-9]+$ ]] && echo int || echo char int [wsh@test ~ ✔]$ [[ abc123 =~ ^[0-9]+$ ]] && echo int || echo char char ``` ##### 2.4 判断字符串长度是否为0的多种思路 **示例1:** 使用字符串条件表达式判断 ```bash [wsh@test ~ ✔]$ [ -z "wsh" ] && echo true || echo false false [wsh@test ~ ✔]$ [ -n "wsh" ] && echo false || echo true false ``` **示例2:** 使用变量子串判断 ```bash [wsh@test ~ ✔]$ str=wsh [wsh@test ~ ✔]$ [ ${#str} -eq 0 ] && echo true || echo false false ``` **示例3:** 使用 expr length 函数判断 ```bash [wsh@test ~ ✔]$ [ $(expr length "wsh") -eq 0 ] && echo true || echo false false ``` **示例4:** 使用wc -L命令统计判断 ```bash [wsh@test ~ ✔]$ [ $(echo "wsh"|wc -L) -eq 0 ] && echo true || echo false false ``` **示例5:** 使用 awk length 函数判断 ```bash [wsh@test ~ ✔]$ [ $(echo "wsh"|awk '{print length}') -eq 0 ] && echo true || echo false false ``` ##### 2.5 监控 memcached 服务是否正常 监控memcached服务是否正常,模拟用户(Web客户端)检测。 此题需要读者了解并可以搭建memcached服务(memcached是运维场景中常用的内存缓存软件),且可以用nc或telnet命令访问memcached服务,加上用set/get来模拟检测。 1. 环境准备 ```bash [root@test ~ ✔]# yum install -y nc memcached [root@test opt ✔]# systemctl enable memcached.service --now ``` 2. 参考解答 ```bash [wsh@test scripts ✔]$ cat check_memcache.sh #!/bin/bash # Author: wsh # Time: 2022-12-10 14:21:37 # Name: check_memcache.sh systemctl is-active memcached.service &>/dev/null RetVal=$? if [ $RetVal -ne 0 ];then echo "Memcached is not running." else # 删除缓存中的 key 及对应的值 printf "del key\r\n"|nc 127.0.0.1 11211 &>/dev/null # 添加新值 printf "set key 0 0 10 \r\nwsh1234\r\n"|nc 127.0.0.1 11211 &>/dev/null # 查询新值 McCount=$(printf "get key\r\n"|nc 127.0.0.1 11211|wc -l) [ $McCount -eq 1 ] && \ echo "Memcached status is ok." || \ echo "Memcached status is error." fi ``` ##### 2.6 开发 rsync 服务的启动脚本 rsync是运维场景中最常用的数据同步软件,本例就是要完成一个类似系统的启动rsync服务的方法,即使用/etc/init.d/rsyncd{start\|stop\|restart}即可启动和停止rsync服务,这里是用if条件语句来实现相应效果的,其实通过case语句来实现效果最佳,不过由于还没讲解到case语句,因此这里主要练习if语句。 1. 分析问题。要想开发出rsync服务的启动脚本,就需要熟悉rsync服务的配置,以及rsync服务是如何启动,以及如何停止的。 2. 要实现/etc/init.d/rsyncd{start\|stop\|restart}的操作语法,就需要用到脚本的传参。根据传入的参数,进行判断,然后执行对应的启动和停止命令。 实现过程如下: * **第1步**,rsync 服务准备。 ```bash # 安装软件 [wsh@test ~ ✔]$ sudo yum install -y rsync # 启动服务 [wsh@test ~ ✔]$ sudo rsync --daemon # 检测端口 [wsh@test ~ ✔]$ ss -lnt|grep ':873' LISTEN 0 5 *:873 *:* LISTEN 0 5 [::]:873 [::]:* ``` * **第2步**,rsync服务停止和端口检测。 ```bash [wsh@test ~ ✔]$ sudo pkill rsync [wsh@test ~ ✔]$ ss -lnt|grep ':873' ``` * **第3步**,判断rsync服务是否启动的方法。 * 常规方法有检测端口以及进程是否存在 * 还可以当服务启动时,创建一个锁文件(/var/lock/rsync/locker),而当服务停止时,就删除这个锁文件,这样就可以通过判断这个文件有无,来确定服务是否是启动状态,这是一些系统脚本常用的手法。 * 第4步,开发rsync服务的启动脚本。 ```bash [wsh@test ~ ✔]$ sudo vim /etc/init.d/rsyncd #!/bin/bash # Author: wsh # Time: 2022-12-12 18:10:15 # Name: rsyncd # 判断参数个数 if [ $# -ne 1 ];then echo "Usage: $0 [ start | stop | restart | status ]" exit 1 fi # 根据参数1做出相应动作 if [ "$1" = "start" ];then rsync --daemon sleep 2 # 判断当前状态 if ss -lnt|grep -q ':873';then echo "rsyncd is started." exit 0 fi elif [ "$1" = "stop" ];then pkill rsync &>/dev/null sleep 2 # 判断当前状态 if ! ss -lnt|grep -q ':873';then echo "rsyncd is stoped." exit 0 fi elif [ "$1" = "status" ];then # 判断当前状态 if ! ss -lnt|grep -q ':873';then echo "rsyncd is stoped." else echo "rsyncd is started." fi elif [ "$1" = "restart" ];then pkill rsync &>/dev/null retval_1=$? sleep 1 rsync --daemon retval_2=$? sleep 1 # 判断停止和启动状态 if [ $retval_1 -eq 0 -a $retval_2 -eq 0 ];then echo "rsyncd is restarted." exit 0 fi else echo "Usage: $0 [ start | stop | restart | status ]" exit 1 fi ``` 执行结果: ```bash [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd start rsyncd is started. [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd status rsyncd is started. [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd stop rsyncd is stoped. [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd status rsyncd is stoped. [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd restart [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd status rsyncd is started. [wsh@test ~ ✔]$ sudo bash /etc/init.d/rsyncd hello Usage: /etc/init.d/rsyncd [ start | stop | restart | status ] [wsh@test ~ ✔]$ echo $? 1 ``` ### Shell 函数的知识与实践 #### 1 Shell 函数介绍 在讲解Shell 函数之前,先来回顾Linux系统中 `alias` 的作用。 ```bash [wsh@test ~ ✔]$ ls -l --color=auto /home total 0 drwx------. 2 wsh wsh 125 Aug 24 16:24 wsh [wsh@test ~ ✔]$ alias ll='ls -l --color=auto' [wsh@test ~ ✔]$ ll /home/ total 0 drwx------. 2 wsh wsh 125 Aug 24 16:24 wsh ``` 函数也有类似于别名的作用,例如可简化程序的代码量,让程序更易读、易改、易用。 简单地说,函数的作用就是将程序里多次被调用的相同代码组合起来(函数体),并为其取一个名字(即函数名),其他所有想重复调用这部分代码的地方都只需要调用这个名字就可以了。当需要修改这部分重复代码时,只需要改变函数体内的一份代码即可实现对所有调用的修改,也可以把函数独立地写到文件里,当需要调用函数时,再加载进来使用。 使用 Shell 函数的优势整理如下: * 把相同的程序段定义成函数,可以减少整个程序的代码量,提升开发效率。 * 增加程序的可读性、易读性,提升管理效率。 * 可以实现程序功能模块化,使得程序具备通用性(可移植性)。 对于Shell来说,Linux系统里的近2000个命令可以说都是Shell的函数,所以,Shell的函数也是很多的,这一点需要读者注意。 #### 2 Shell 函数的语法 下面是Shell 函数的常见语法格式。 **标准写法**: ```bash function 函数名 () { 指令... return n } ``` **简化写法1:不写()** ```bash function 函数名 { 指令... return n } ``` **简化写法2:不写function** ```bash 函数名 () { 指令... return n } ``` #### 3 Shell 函数的执行 Shell的函数分为最基本的函数和可以传参的函数两种,其执行方式分别说明如下。 1. 执行不带参数的函数时,直接输人函数名即可(注意不带小括号)。 格式如下:`函数名` 有关执行函数的重要说明: * 执行Shell 函数时,函数名前的function和函数后的小括号都不要带。 * 函数的定义必须在要执行的程序前面定义或加载。 * Shell执行系统中各种程序的执行顺序为:**系统别名-\>函数-\>系统命令-\>可执行文件**。 * 函数执行时,会和调用它的脚本共用变量,也可以为函数设定局部变量及特殊位置参数。 * 在Shell 函数里面,return命令的功能与exit类似,return的作用是退出函数,而exit是退出脚本文件。 * return语句会返回一个退出值(即返回值)给调用函数的当前程序,而exit会返回一个退出值(即返回值)给执行程序的当前Shell。 * 如果将函数存放在独立的文件中,被脚本加载使用时,需要使用source或来加载。 * 在函数内一般使用local定义局部变量,这些变量离开函数后就会消失。 2. 带参数的函数执行方法,格式如下: 函数名 参数1 参数2 函数后接参数的说明: * Shell 的位置参数($1、2...、2...、2...、#、∗、\*、∗、?及$@)都可以作为函数的参数来使用。 * 此时父脚本的参数临时地被函数参数所掩盖或隐藏。 * $0 比较特殊,它仍然是父脚本的名称。 * 当函数执行完成时,原来的命令行脚本的参数即可恢复。 * 函数的参数变量是在函数体里面定义的。 #### 4 Shell 函数的基础实践 **示例1:** hello函数 ```bash [wsh@test ~ ✔]$ cat fun1.sh #!/bin/bash function hello () { echo "Hello World !" } hello [wsh@test ~ ✔]$ bash fun1.sh Hello World ! # 函数必须先定义,后调用 [wsh@test ~ ✔]$ cat fun2.sh #!/bin/bash hello function hello () { echo "Hello World !" } [wsh@test ~ ✔]$ bash fun2.sh fun2.sh: line 2: hello: command not found ``` **示例2:** 调用外部函数 ```bash [wsh@test ~ ✔]$ cat >> mylib << 'EOF' function hello () { echo "Hello World !" } EOF [wsh@test ~ ✔]$ cat fun3.sh #!/bin/bash if [ -r mylib ];then source mylib else echo mylib is not exist exit 1 fi hello [wsh@test ~ ✔]$ bash fun3.sh Hello World ! ``` **示例3:** 带参数的函数 ```bash [wsh@test ~ ✔]$ cat fun4.sh #!/bin/bash function print () { if [ "$1" == "PASS" ];then echo -e '\033[1;32mPASS\033[0;39m' elif [ "$1" == "FAIL" ];then echo -e '\033[1;31mFAIL\033[0;39m' elif [ "$1" == "DONE" ];then echo -e '\033[1;35mDONE\033[0;39m' else echo "Usage: print PASS|FAIL|DONE" fi } read -p "请输入你想要打印的内容:" str print $str [wsh@test ~ ✔]$ bash fun4.sh 请输入你想要打印的内容:`PASS` PASS [wsh@test ~ ✔]$ bash fun4.sh 请输入你想要打印的内容:hello Usage: print PASS|FAIL|DONE ``` **示例4:** hello函数 ```bash [wsh@test ~ ✔]$ cat fun5.sh #!/bin/bash function print () { if [ "$1" == "PASS" ];then echo -e '\033[1;32mPASS\033[0;39m' elif [ "$1" == "FAIL" ];then echo -e '\033[1;31mFAIL\033[0;39m' elif [ "$1" == "DONE" ];then echo -e '\033[1;35mDONE\033[0;39m' else echo "Usage: $0 PASS|FAIL|DONE" fi } str=$2 print $str # 结果表明,脚本的第一个参数并没有传递给脚本内部函数。 [wsh@test ~ ✔]$ bash fun5.sh PASS FAIL FAIL # 结果表明,$0 仍然使用脚本名,而非函数名。 [wsh@test ~ ✔]$ bash fun5.sh Usage: fun5.sh PASS|FAIL|DONE ``` #### 5 利用 Shell 函数开发企业级URL检测脚本 ```bash [wsh@test ~ ✔]$ cat check_url.sh #!/bin/bash function usage () { echo "usage: $0 url" exit 1 } function check_url () { wget --spider -q -o /dev/null --tries=1 -T 5 $1 [ $? -eq 0 ] && echo $1 is accessable || echo $1 is not accessable } function main () { [ $# -ne 1 ] && usage check_url $1 } main $* # 测试结果如下: [wsh@test ~ ✔]$ bash check_url.sh www.baidu.com www.baidu.com is accessable [wsh@test ~ ✔]$ bash check_url.sh www.wsh.com www.wsh.com is not accessable ``` #### 6 函数的递归调用 ##### 示例1:求1+2+3+...+10 的和 ```bash #!/bin/bash function sum_out() { if [ $1 -eq 1 ];then sum=1 else sum=$[ $1 + $(sum_out $[ $1 - 1 ] ) ] fi echo $sum } read -p "输入一个你想计算和的整数:" num sum_out $num ``` ##### 示例2:求1\*2\*3\*...\*10 的阶乘 ```bash #!/bin/bash function fact_out() { if [ $1 -eq 1 ];then sum=1 else sum=$[ $1 * $(fact_out $[ $1 - 1 ] ) ] fi echo $sum } read -p "输入一个你想计算和的整数:" num fact_out $num ``` ##### 示例3:fork 炸弹 ```bash :(){ :|:& };: ``` 解释如下: ```bash :() # 定义函数,函数名为":",即每当输入":"时就会自动调用{}内代码 { # ":"函数起始字元 : # 用递归方式调用":"函数本身 | # 使用管道一次产生两个函数调用 : # 另一次递归调用的":"函数 & # 放后台运行,以便产生更多的子进程 } # ":"函数终止 ; # ":"函数定义结束后将要进行的操作... : # 调用":"函数,"引爆"fork炸弹 ``` **fork 炸弹原因**:无限制的启用新进程,直到消耗完所有计算资源。 **解决办法**:限制用户进程数量。 例如:限制100个进程 ```bash [wsh@test ~ ✔]$ ulimit -u 100 ``` ### case 条件语句的应用实践 case 条件语句相当于多分支的if/elif/else条件语句,但是它比这些条件语句看起来更规范更工整,常被应用于实现系统服务启动脚本等企业应用场景中。 在case语句中,程序会将case获取的变量的值与表达式部分的值1、值2、值3等逐个进行比较: * 如果获取的变量值和某个值(例如值1)相匹配,就会执行值(例如值1)后面对应的指令(例如指令1,其可能是一组指令),直到执行到双分号(;;)才停止,然后再跳出case语句主体,执行case语句(即esac字符)后面的其他命令。 * 如果没有找到匹配变量的任何值,则执行"\*)"后面的指令(通常是给使用者的使用提示),直到遇到双分号(此处的双分号可以省略)或esac结束,这部分相当于if多分支语句中最后的else语句部分。 * 另外,case语句中表达式对应值的部分,还可以使用管道等更多功能来匹配。 #### 1 case 条件语句的语法 case 条件语句的语法格式为: ```bash case "变量值" in 值1) 指令1... ;; 值2) 指令2... ;; *) 指令3... ;; esac ``` 为了便于记忆,下面是某女生写的case 条件语句的中文形象描述: ```bash case "找老公条件" in 资产大于1千万) 嫁给你 ;; 资产介于1百万和1千万之间) 再考虑以下 ;; 资产小于1百万) 再见 ;; 其他情况) 视情况而定 ;; esac ``` case 条件语句的执行流程逻辑图如下:  #### 2 case 条件语句实践 **示例**:判断用户输入的数字是否是1、2、3。 ```bash [wsh@test ~ ✔]$ cat case1.sh #!/bin/bash read -p "请输入一个1-3之间数字:" num case $num in 1) echo "您输入的数字是:$num" ;; 2) echo "您输入的数字是:$num" ;; 3) echo "您输入的数字是:$num" ;; *) echo "请输入一个1-3之间数字。" ;; esac # 执行验证 [wsh@test ~ ✔]$ bash case1.sh 请输入一个1-3之间数字:1 您输入的数字是:1 [wsh@test ~ ✔]$ bash case1.sh 请输入一个1-3之间数字:4 请输入一个1-3之间数字。 ``` #### 3 实践:给输出的字符串加颜色 ```bash [wsh@test ~ ✔]$ cat case2.sh #!/bin/bash case $1 in PASS) echo -e '\033[1;32mPASS\033[0;39m' ;; FAIL) echo -e '\033[1;31mFAIL\033[0;39m' ;; DONE) echo -e '\033[1;35mDONE\033[0;39m' ;; *) echo "Usage: $0 PASS|FAIL|DONE" ;; esac ``` 执行效果:  #### 4 case 条件语句的 Linux 系统脚本范例 /etc/init.d/network 提示:network-scripts 软件包 提供以上脚本。 #### 5 case小结 1. case 语句比较适合变量值较少且为固定的数字或字符串集合的情况,如果变量的值是已知固定的start/stop/restart 等元素,那么采用case语句来实现就比较适合。 2. case语句和if条件语句的常见应用场景 * case主要是写服务的启动脚本,一般情况下,传参不同且具有少量的字符串,其适用范围较窄。 * if就是取值判断、比较,应用面比case更广。几乎所有的case语句都可以用if条件语句来实现。 3. case语句就相当于多分支的if/elif/else语句,但是case语句的优势是更规范、易读。