RHCE9学习指南 第21章 用bash写脚本

grep的用法是:
grep 关键字 file 意思是从file中过滤出含有关键字的行。

例如,grep root /var/log/messages,意思是从/var/log/messages中过滤出含有root的行。这里很明确的是过滤含有"root"的行。

如果我要是想在/var/log/messages中过滤出含有IP地址的行呢?IP地址就是一类字符,例如,1.1.1.1是一个IP,192.168.26.100也是一个IP,那么我用什么能表示出来这一类字符呢?

不管是通配符和正则表达式都是为了模糊匹配,为了匹配某一类内容,而不是具体的某个关键字。通配符一般用于在shell中,正则表达式一般用于其他语言中。

不管是通配符还是正则表达式,主要是理解他们的元字符,然后用元字符来组合成我们想要的那一类字符,本章我们主要讲解通配符的使用。

像我们平时说的张某某,这个某就是一个元字符,不是一个定值。指的是姓张,名称含有2个字。张某某可能匹配到张二狗也能匹配到张阿猫,但是没法匹配到李阿三。也匹配不了张三,因为张某某匹配的是姓名为3个字的,但是张三这个姓名只有2个字。

如果说有一个人叫姓"张"名"某",那么需要匹配"张某"这个人,而不是要匹配张三张四,可以用张\某,某前加个"\"表示转义的意思。

21.1 通配符

通配符里一般用于shell语言里,通配符里常见的元字符如下。

shell 复制代码
(1)[]:匹配一个字符,匹配的是出现在中括号中的字符。
(2)[abc]:匹配一个字符,且只能是a或b或c。
(3)[a-z]:这里"-"有特殊意义,表示"到"的意思,这里表示a-z,即匹配任一字母。
(4)[0-9]:这里表示匹配任一数字。
如果想去除含有特殊意义的字符,前面加"\"表示转义,即去除此字符的特殊意义。
(5)[a\-z]:这里的"-"就没有"到"的意思了,匹配的是"a"或"-"或"z"这三个中的一个。
如果想表示"除了"的意思,则在第一个中括号后面加"!"或"^"。
(6)[!a-z]  [^a-z]:这个表示的是除字母外的其他字符。
(7)?:表示一个任意字符,这里强调是一个,不是0个也是多个,但是不能匹配表示隐藏文件的点。
(8)*:表示任意多个任意字符,可以是0个,可以是1个也可以是多个,也不能匹配表示隐藏文件的点。

练习:

先创建目录xx并在目录中创建如下几个测试文件,命令如下。

shell 复制代码
[root@server ~]# mkdir xx
[root@server ~]# cd xx
[root@server xx]# touch 1_aa  aa11  Aa11  _aaa  aa.txt  f1aa  u_12
[root@server xx]#

找出首字符是字母,第二个字符是数字的那些文件,命令如下。

shell 复制代码
[root@server xx]# ls [a-z][0-9]*
f1aa
[root@server xx]#

找出首字符是字母,第二个字符不是数字的那些文件,命令如下。

shell 复制代码
[root@server xx]# ls [a-z][^0-9]*
aa11  Aa11  aa.txt  u_12
[root@server xx]#

找出第一个字符不是字母,第二个字符不是数字的文件,命令如下。

shell 复制代码
[root@server xx]# ls [^a-z][^0-9]*
1_aa  _aaa
[root@server xx]#

以上可以看到,找出来的文件完全符合我们的需求。下面找出首字符为大写字母、第二个字符是非数字的文件,命令如下。

shell 复制代码
[root@server xx]# ls
1_aa  aa11  Aa11  _aaa  aa.txt  f1aa  u_12
[root@server xx]# ls [A-Z][!0-9]*
Aa11  u_12
[root@server xx]#

可以看到,首字母为大写的列出来了,首字符为小写字母的文件有的列出来有的没有列出来,所以[a-z]或[A-Z]这种有时并不精确。如果要更精确可以用如下元字符。

shell 复制代码
(1)[[:upper:]]:纯大写。
(2)[[:lower:]]:小写。
(3)[[:alpha:]]:字母。
(4)[[:alnum:]]:字母和数字。
(5)[[:digit:]]:数字。

列出首字符是小写的,第二个字符为数字的文件,命令如下。

shell 复制代码
[root@server xx]# ls [[:lower:]][0-9]*
f1aa
[root@server xx]#

列出首字符为大写字母,第二个字符为数字或字母的文件,命令如下。

shell 复制代码
[root@server xx]# ls [[:upper:]][[:alnum:]]*
Aa11
[root@server xx]#

如果想在yum源中列出所有以vsftpd开头的包,可以用如下命令。

shell 复制代码
[root@server xx]# yum list vsftpd*
	...输出..
可安装的软件包
vsftpd.x86_64                    3.0.3-49.el9                            aa
[root@server xx]#

在当前目录中创建一个文件vsftpdxxx,命令如下。

shell 复制代码
[root@server xx]# touch vsftpdxxx
[root@server xx]#

然后再执行yum list vsftpd*,如下所示。

shell 复制代码
[root@server xx]# yum list vsftpd*
	...输出...
错误:没有匹配的软件包可以列出
[root@server xx]# 

此处显示没有匹配的包,为什么呢?这是因为yum是bash的一个子进程,vsftpd在bash首先被解析成了vsftpdxxx,然后再经过yum。所以,本质上执行的是 yum list vsftpdxxx,而yum源中是没有这个vsftpdxxx这个包的,所以报错。
为了防止bash对这里的
进行解析,可以加上转移符"",所以下面命令是正确的。

shell 复制代码
[root@server xx]# yum list vsftpd\*
	...输出...
可安装的软件包
vsftpd.x86_64                   3.0.3-49.el9        aa
[root@server xx]#

21.2 变量

所谓的变量指的是可变的值,并非具体的值。例如,嘴中发出的"我",指的是我自己,张三嘴中发出的"我",指的是张三,那么这个"我"就是一个变量。

变量可以分本地变量、环境变量、位置变量、预定义变量。

21.2.1 本地变量

定义本地变量的格式如下。
变量名=值

定义变量有以下注意几点。

shell 复制代码
(1)变量名可以包含_、数字、大小写字母,但绝对不可以数字开头。
(2)=两边不能有空格。
(3)"值"如果含有空格,要使用单引号''或双引号""括起来。
(4)定义变量时,变量名前是不需要加$的,引用变量时在需要在变量名前加$。

本章的实验都放在 ~/yy中练习,命令如下。

shell 复制代码
[root@server xx]# cd 
[root@server ~]# mkdir yy ; cd yy
[root@server yy]#

下面开始练习定义变量,命令如下。

shell 复制代码
[root@server yy]# 1aa=123
bash: 1aa=123: 未找到命令...
[root@server yy]# 

这里定义变量不正确,因为变量名不能以数字开头,命令如下。

shell 复制代码
[root@server yy]# aa-1=123
bash: aa-1=123: 未找到命令...
[root@server yy]#

这里定义变量不对,因为变量名只能是字母、数字、下划线来组合,命令如下。

shell 复制代码
[root@server yy]# aa =123
bash: aa: 未找到命令...
[root@server yy]#

这里的错误是因为等号左边有空格。

shell 复制代码
[root@server yy]# aa=1 2
bash: 2: 未找到命令...
[root@server yy]#

这里的错误是因为"值"部分如果有空格话,要用引号引起来。

shell 复制代码
[root@server yy]# aa=123
[root@server yy]#

这是正确的定义了一个变量。

在使用本地变量时,变量名前需要加$符号,命令如下。

shell 复制代码
[root@server yy]# echo $aa
123
[root@server yy]#

本地变量的特点是:只能影响当前shell,不能影响子shell。

shell 复制代码
[root@server yy]# echo $aa
123
[root@server yy]# echo $$
620514
[root@server yy]# 

当前shell的pid是620514,下面打开一个子shell。

shell 复制代码
[root@server yy]# bash
[root@server yy]# echo $$
620956
[root@server yy]# 

这个子shell的PID是620956。

shell 复制代码
[root@server yy]# echo $aa

[root@server yy]# 

这里可以看到,没有aa变量。

shell 复制代码
[root@server yy]# exit
exit
[root@server yy]# echo $$
620514
[root@server yy]# echo $aa
123
[root@server yy]#

再次退回到原来的bash,又有了aa变量。情形如图21-1所示。

图21-1 本地变量不会作用到子shell

定义变量除刚才显式的定义外,还可以使用如下两种方法。

**方法1:**把一个命令的结果赋值给一个变量,这个变量要使用"$()"括起来,或者用反引号``引起来。这里是反引号,与波浪号"~"是同一个键,不是单引号。

例如,定义一个名称是ip的变量,对应的值是ens160的IP,命令如下。

shell 复制代码
[root@server yy]# ip=$(ifconfig ens160 | awk '/inet /{print $2}')
[root@server yy]# echo $ip
192.168.26.101
[root@server yy]#

**方法2:**通过read获取变量

read的用法如下。
read -p "提示信息" 变量

当遇到read命令时,系统会等待用户输入,用户所输入的值会赋值给read后面的变量,命令如下。

shell 复制代码
[root@server yy]# read -p "请输入您的名称: " aa 按【Enter】键
请输入您的名称: tom
[root@server yy]# echo $aa
tom
[root@server yy]#

当执行read这条命令时系统会提示用户输入一些内容,所输入的内容会赋值给aa变量。这里我们输入的是tom,所以打印aa变量时,看到的值是tom。

这样的用法比较适合写需要和用户交互的脚本。

21.2.2 环境变量

定义环境变量的注意点和本地变量是一样的。在定义环境时,前面加上export即可,命令如下。

shell 复制代码
[root@server yy]# export bb=123
[root@server yy]# 

或者先定义为本地变量,然后在通过export转变为环境变量,命令如下。

shell 复制代码
[root@server yy]# bb=123
[root@server yy]# export bb
[root@server yy]#

要想查看所有的环境变量可以执行env命令。

环境变量的特点是可以影响子shell,这里强调的是子shell,不能影响父shell。

shell 复制代码
[root@server yy]# echo $$
620514
[root@server yy]# echo $bb
123
[root@server yy]# 

当前shell的PID是620514,里面有一个环境变量aa。

shell 复制代码
[root@server yy]# bash
[root@server yy]# echo $$
621231
[root@server yy]# echo $bb
123
[root@server yy]# 

打开一个子shell,PID为621231,里面可以看到bb变量的值,说明环境变量已经影响到子shell了。

shell 复制代码
[root@server yy]# export bb=456
[root@server yy]# exit
exit
[root@server yy]# 

在子shell中重新给bb赋值为456,然后退回到父shell。

shell 复制代码
[root@server yy]# echo $$
620514
[root@server yy]# echo $bb
123
[root@server yy]# 

可以看到,在父shell中,bb的值仍然是123,说明在子shell中定义的变量不会影响到父shell,如图21-2所示。

图21-2 环境变量可以作用到子shell

系统中默认已经存在很多个变量,如下所示。

shell 复制代码
(1)UID:表示当前用户的UID。
(2)USER:表示当前用户名。
(3)HOME:表示当前用户的家目录。

分别显示这些变量的值,命令如下。

shell 复制代码
[root@server yy]# echo $UID
0
[root@server yy]# echo $USER
root
[root@server yy]# echo $HOME
/root
[root@server yy]#

有一个很重要的环境变量PATH,当我们执行命令时,一定要指定这个命令的路径,如果没有写路径,则会到PATH中所指定的路径中进行查询。先查看当前用户的PATH变量的值,命令如下。

shell 复制代码
[root@server yy]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@server yy]#

PATH变量由多个目录组成,每个目录用冒号:分隔,我们把写好的脚本放在PATH变量中指定目录之后,运行此脚本时就不需要指明路径了。

查看tom用户的PATH变量,命令如下。

shell 复制代码
[tom@server ~]$ echo $PATH
/home/tom/.local/bin:/home/tom/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
[tom@server ~]$

这里展示了tom的PATH变量,如果tom写了一个脚本之后,就可以把这个脚本放在自己家目录下的.local/bin或bin目录下(~/.local/bin或~/bin),如果这两个目录不存在则创建出来即可。

以上定义的环境变量也只是在当前终端中生效,关闭终端之后这个变量也就消失了。如果想让定义的变量永久生效,可以写入家目录的.bash_profile中。因为打开终端时,首先会运行家目录下面的一个隐藏文件 .bash_profile。

21.2.3 位置变量和预定义变量

我们运行脚本时,有时后面是需要加上参数的。但是我们在写脚本时并不能预知后期在脚本后面跟上什么参数。这时就能用到位置变量了,位置变量如下。

shell 复制代码
$0:表示脚本的名称。
$1:表示第1个参数。
$2:表示第2个参数。
......
${10}:表示第10个参数。
......

这里$后面的数字如果不是个位数,则要用{}括起来。

系统中还内置了一些预定义变量。

$#:表示参数的个数

$*:表示所有的参数

例1:

写一个带参数的脚本,内容如下。

shell 复制代码
[root@server yy]# cat sc1.yaml
#!/bin/bash
echo "这是我的第一个脚本,脚本的名称是 $0"
echo "第1个参数是: $1"
echo "第2个参数是: $2"
echo "第3个参数是: $3"
echo "此脚本一共有 $# 个参数,它们分别是: $*"
[root@server yy]# 

给这个脚本加上可执行权限,并加参数运行,命令如下。

shell 复制代码
[root@server yy]# chmod +x sc1.yaml 
[root@server yy]# ./sc1.yaml tom bob mary
这是我的第一个脚本,脚本的名称是 ./sc1.yaml
第1个参数是: tom
第2个参数是: bob
第3个参数是: mary
此脚本一共有 3 个参数,它们分别是: tom bob mary
[root@server yy]# 

运行这个脚本时,共指定了3个参数: tom , bob, mary,他们分别赋值给了$1, 2, 3 。这里 3。这里 3。这里#被自动赋值为3,因为总共有3个参数,所有的参数被复制给*。

例2:

运行如下命令。

shell 复制代码
[root@server yy]# set a b c d e f g h i j k 
[root@server yy]#

查看此命令的第1个和第3个参数,命令如下。

shell 复制代码
[root@server yy]# echo $1
a
[root@server yy]# echo $9
i
[root@server yy]#

第1个参数是a,第9个参数是i,下面查看第10个参数,命令如下。

shell 复制代码
[root@server yy]# echo $10
a0
[root@server yy]#

第9个参数是i,那么第10个参数应该是j才对,这里显示为a0,为什么呢?因为这里先把$10当成了$1+0了,$1的值是a,所以$10的值为a0。

所以,在位置变量中数字超过10,要用{}扩起来,下面的才是正确的。

shell 复制代码
[root@server yy]# echo ${10}
j
[root@server yy]#

另外在引用变量时,双引号和单引号的区别,直接看个例子。

shell 复制代码
[root@server yy]# xx=tom
[root@server yy]# echo "my name is $xx"
my name is tom
[root@server yy]# echo 'my name is $xx'
my name is $xx
[root@server yy]#

这里先定义一个变量xx=tom,如果变量在双引号中引用,则变量被解析成具体的值,如果变量出现在单引号中,则不被解析。

21.3 返回值

执行某命令之后,结果不是正确的就是错误的。命令正确执行了,返回值为0,如果没有正确执行则返回值为非零。返回值为非零,不一定是语法错误,执行结果如果有"否定"的意思,返回值也为非零。例如,ping 192.168.26.3,语法没有错误,但是没ping通,返回值也为非零。

返回值记录在 ? 中的,且 ?中的,且 ?中的,且?只记录刚刚执行过命令的返回值。因为$?的值会被新执行命令的返回值覆盖。

练习:

先执行一个命令xxx,命令如下。

shell 复制代码
[root@server yy]# xxx
bash: xxx: 未找到命令...
[root@server yy]# echo $?
127
[root@server yy]# echo $?
0
[root@server yy]#

先执行一个命令xxx,这个命令是错误的命令, ? 记录的是刚刚执行过命令 x x x 的返回值。所以,查看 ?记录的是刚刚执行过命令xxx的返回值。所以,查看 ?记录的是刚刚执行过命令xxx的返回值。所以,查看?的值是127,是一个非零的值。再次查看 ? 的值时,却变成了 0 ,因为这个 ?的值时,却变成了0,因为这个 ?的值时,却变成了0,因为这个?记录的不再是xxx命令的返回值,而是它前面执行过的"echo $?" 这个命令的返回值。

逻辑上"否定"的意思也是可以体现出来的,例如,下面的例子。

shell 复制代码
[root@server yy]# grep ^root /etc/passwd 
root:x:0:0:root:/root:/bin/bash
[root@server yy]# echo $?
0
[root@server yy]# 

这里在/etc/passwd过滤行开头为root的行,结果找到了,所以返回值为0。

shell 复制代码
[root@server yy]# grep ^rootxxx /etc/passwd 
[root@server yy]# echo $?
1
[root@server yy]#

这里在/etc/passwd过滤行开头为rootxxx的行,结果没找到,这里即使语法没错误,但是逻辑上有"否定"的意思,所以返回值为非零。

21.4 数值运算

在写脚本时,有时我们经常要做一些数学运算。

数学运算的符号如下。

shell 复制代码
(1)+:表示加。
(2)-:表示减。
(3)*:表示乘。
(4)/:表示除。
(5)**:表示次方。

进行数学运算的表达式有 ( ( ) ) 、 (())、 (())、[]、let等,命令如下。

shell 复制代码
[root@server yy]# echo $((2+3))
5
[root@server yy]# echo $((2*3))
6
[root@server yy]# echo $((2**3))
8
[root@server yy]# echo $[2**3]
8
[root@server yy]#

其中 ( ( ) ) 和 (())和 (())和[]的用法是一样的,如果不用这样的表达式,看如下所示的代码。

shell 复制代码
[root@server yy]# echo 2**3
2**3
[root@server yy]#

这里并不是计算的是2的3次方,直接把这4个字符打印出来了。

let也可以用于数学运算,命令如下。

shell 复制代码
[root@server yy]# let aa=1+2
[root@server yy]# echo $aa
3
[root@server yy]#

这里aa的值就是为3。

下面看下如果不使用let的情况,命令如下。

shell 复制代码
[root@server yy]# aa=1+2
[root@server yy]# echo $aa
1+2
[root@server yy]#

这里并没有把aa的值1+2当成数字,而是当成了3个字符:"1"、"+"、"2",所以结果显示的也是1+2。

可以实现定义aa为整数类型,然后再做数学运算,命令如下。

shell 复制代码
[root@server yy]# declare -i aa
[root@server yy]# aa=1+2
[root@server yy]# echo $aa
3
[root@server yy]# 

首先declare -i aa把aa定义为一个整数,所以1+2等于3,然后赋值给aa,所以aa的值为3。

以上的表达式不能求得小数,如果要得到小数需要使用bc命令,用法如下。
echo "scale=N ; 算法 " | bc

这里N是一个数字,表示小数点后面保留几位。

计算2/3,小数点后面保留3位,命令如下。

shell 复制代码
[root@server yy]# echo "scale=3 ; 2/3" | bc
.666
[root@server yy]# 

这里得到的结果是0.666,这里整数部分的0没有显示。

计算7/6,小数点后面保留3位,命令如下。

shell 复制代码
[root@server yy]# echo "scale=3 ; 7/6" | bc
1.166
[root@server yy]#

21.5 比较、对比、判断

在写脚本时,有时需要要做一些比较,例如,两个数字谁大谁小,两个字符是否相同等。做对比的表达式有[]、[[]]、test。其中[]和test这两种表达式的作用是相同的。[[]]和[]的不同在于,[[]]中能识别通配符和正则表达式中的元字符,[]却不能。

需要注意的是,在比较时,中括号两边,以及后续提及的比较符两边都要留有空格。

21.5.1 数字的比较

数字的比较,主要是比较两个数字谁大,谁小,或者是否相同。能用到的比较符有以下几种。

shell 复制代码
(1)-eq:相等。
(2)-ne:不等。
(3)-gt:大于。
(4)-ge:大于等于。
(5)-lt:小于。
(6)-le:小于等于。

做完比较之后,通过返回值来判断比较是否成立。

练习1:

判断1等于2,命令如下。

shell 复制代码
[root@server yy]# [ 1 -eq 2 ]
[root@server yy]# echo $?
1
[root@server yy]#

1是不能等于2的,所以判断不成立,所以返回值为非零。需要注意的是,[]两边及比较符两边的空格。

练习2:

判断1不等于2,命令如下。

shell 复制代码
[root@server yy]# [ 1 -ne 2 ]
[root@server yy]# echo $?
0
[root@server yy]#

1不等于2,所以判断成立,这里返回值为0。

21.5.2 字符的比较

字符串的比较,一般比较两个字符串是相同还是不相同,用得较多的比较符有以下两种。

(1)==:相同。

(2)!=:不相同。

做完比较之后,通过返回值来判断比较是否成立。

练习1:

定义一个变量aa=tom,然后做判断,命令如下。

shell 复制代码
[root@server yy]# aa=tom
[root@server yy]# [ $aa == tom ]
[root@server yy]# echo $?
0
[root@server yy]#

变量aa的值和tom完全相同,所以判断成立,所以返回值为0。

练习2:

在判断中匹配通配符,命令如下。

shell 复制代码
[root@server yy]# aa=tom
[root@server yy]# [ $aa == to? ]
[root@server yy]# echo $?
1
[root@server yy]#

这里定义aa=tom,按照前面讲过的通配符,to?匹配的应该是前两个字符为to,第三个可以是任一字符的,所以tom应该会被to?匹配到,为什么返回值为非零呢?

原因在于这一对中括号[]中是不能识别通配符的,aa的值是't、o、m'三个字符,而等号后面是't、o、?'这三个字符,并没有把问号当成通配符,所以判断失败。

如果想识别通配符,那么就要用双中括号[[]],看下面的判断。

shell 复制代码
[root@server yy]# aa=tom
[root@server yy]# [[ $aa == to? ]]
[root@server yy]# echo $?
0
[root@server yy]#

在[[]]中能识别通配符"?",所以这里判断成立,返回值为0。

注意:

shell 复制代码
(1)==后面跟的是通配符,如果想跟正则,比较符就不能使用==了,要换成=~。
(2)一定要注意中括号两边的空格,以及比较符两边的空格。

21.5.3 属性的比较

属性的判断,用于判断一个文件是否具备某个属性,常见的属性包括以下7种。

shell 复制代码
(1)-r:具备读权限。
(2)-w:具备写权限。
(3)-x:具备可执行权限。
注意:以上三个属性,不管是出现在u、g、o上,只要有,就算是判断成立。
(4)-d:一个目录。
(5)-l:一个软连接。
(6)-f:一个普通文件,且要存在。
(7)-e:不管什么类型的文件,只要存在就算判断成立。

练习1:

判断/etc/hosts具备r权限,命令如下。

shell 复制代码
[root@server yy]# ls -l /etc/hosts
-rw-r--r--. 1 root root 158 9月  10 2018 /etc/hosts
[root@server yy]# [ -r /etc/hosts ]
[root@server yy]# echo $?
0
[root@server yy]#

通过第一条命令可以看到/etc/hosts是具备r权限的,判断/etc/hosts具备r权限,自然成立,所以返回值为0。

练习2:

判断/etc/hosts是否具备可执行权限,命令如下。

shell 复制代码
[root@server yy]# [ -x /etc/hosts ]
[root@server yy]# echo $?
1
[root@server yy]#

这里判断/etc/hosts具备x权限,但是/etc/hosts不管是u、g、o都不具备x权限,所以判断不成立,所以返回值为非零。

如果做一个否定判断,在前面加上叹号'!'即可。

练习3:

判断/etc/hosts没有x权限,命令如下。

shell 复制代码
[root@server yy]# [ ! -x /etc/hosts ]
[root@server yy]# echo $?
0
[root@server yy]# 

这里判断/etc/hosts没有x权限,判断是成立的,所以返回值为0。

练习4:

判断/etc/是一个普通文件,命令如下。

shell 复制代码
[root@server yy]# [ -f /etc ]
[root@server yy]# echo $?
1
[root@server yy]#

我们知道/etc是一个目录不是一个文件,所以这个判断是错误的,所以返回值为非零。

练习5:

判断/etc/不管是什么类型的,只判断存在还是不存在,命令如下。

shell 复制代码
[root@server yy]# [ -e /etc ]
[root@server yy]# echo $?
0
[root@server yy]#

这里/etc是存在的目录,-e用于判断存在不存在,不判断文件类型,所以这里返回值为0。

21.5.4 使用连接符

前面讲的判断只是单个判断,如果要同时做多个判断,那么就需要使用连接符了。能用的连接符包括"&&" 和"||"。

先看下使用&&作为连接符,用法如下。
判断1 && 判断2

只有两个判断都为真(返回值为0), 整体才是真,只要有一个假,则整体就为假。

判断1如果为假,判断2还有必要执行吗? 没有,因为整体已经确认为假了。

判断1为真,整体是真是假在于判断2,所以判断2肯定是要执行的。

shell 复制代码
[root@server yy]# [ 1 -le 2 ] && [ 2 -ge 3 ]
[root@server yy]# echo $?
1
[root@server yy]#

这里有两个判断,第一个判断1小于等于2,这个判断成立的,第二个判断是2大于等于3,这个不成立。使用&&做连接符,需要两边的判断都要成立,整体才成立,所以整个判断为假,所以返回值为非零。

shell 复制代码
[root@server yy]# [ 1 -le 2 ] && [ 2 -le 3 ]
[root@server yy]# echo $?
0
[root@server yy]#

这里有两个判断,第一个判断1小于等于2,这个判断成立的,第二个判断是2小于等于3,这个也成立。使用&&做连接符,需要两边的判断都要成立,整体才成立,所以整个判断为真,所以返回值为非零。

下面看使用||作为连接符,使用||做为连接符的语法如下。
判断1 || 判断2

两个判断只要有一个为真(返回值为0), 整体就是真,只要有全是假,则整体就为假。

判断1为真,整体已经确定为真,所以判断2没有必要要执行的。

判断1为假,整体是真是假在于判断2,所以判断2肯定是要执行的。

shell 复制代码
[root@server yy]# [ 1 -le 2 ] || [ 2 -ge 3 ]
[root@server yy]# echo $?
0
[root@server yy]#

这里有两个判断,第一个判断1小于等于2,这个判断成立的,整体已经确定为真,所以整个判断为真,所以返回值为非零。

shell 复制代码
[root@server yy]# [ 1 -ge 2 ] || [ 2 -ge 3 ]
[root@server yy]# echo $?
1
[root@server yy]#

这次做的两个判断,一个是1大于等于2,一个是2大于等于3,这两个判断都为假,所以整体为假,所以返回值为非零。

21.6 if判断语句

在脚本中执行某条命令需要满足一定的条件,如果不满足就不能执行。此时我们就要用到判断语句了。

先看if判断,if判断的语法如下。

shell 复制代码
if  条件1 ; then
	命令1
elif	条件2 ; then
	命令2
else
	命令3
fi

先判断if后面的判断是不是成立,

如果成立,则执行命令1,然后跳到fi后面,执行fi后面的命令。

如果不成立,则不执行命令1,然后判断elif后面的条件2是不是成立。

如果成立,则执行命令2,然后跳到fi后面,执行fi后面的命令。

如果不成立,则不执行命令2,进行下一轮的elif判断,以此类推。

如果所有if和elif都不成立,则执行else中的命令3。

练习:

写一个脚本/opt/sc1.sh,要求只有root用户才能执行此脚本,其他用户不能执行,命令如下。

shell 复制代码
[root@server yy]# cat /opt/sc1.sh
#!/bin/bash
if [ $UID -ne 0 ]; then
	echo "只有root才能执行此脚本."
	exit 1
fi
echo "hello root"
[root@server yy]# chmod +x /opt/sc1.sh
[root@server yy]# 

脚本分析:

root的UID是0,其他用户的UID不为0。第一条判断 如果UID不等于0,然后打印警告信息"只有root才能执行此脚本",然后exit退出脚本。

如果这里不加exit,判断之后仍然会继续执行echo "hello root",这样判断就失去了意义。只有加了exit之后,如果不是root,则到此结束,不要继续往下执行了。

如果是lduan执行此脚本,if判断成立,打印完报警信息之后,通过exit退出脚本。

如果是root执行此脚本,则判断不成立,直接执行fi后面的语句。

用root执行此脚本的结果如下。

shell 复制代码
[root@server yy]# /opt/sc1.sh 
hello root
[root@server yy]#

使用lduan用户执行的结果如下。

shell 复制代码
[lduan@server ~]$ /opt/sc1.sh 
只有root才能执行此脚本.
[lduan@server ~]$

写一个脚本:写一个脚本/opt/sc2.sh运行脚本时,后面必须要跟上一个参数,参数是系统中的一个文件。

如果这个文件不存在则显示此文件不存在,如果存在,则显示这个文件的行数,命令如下。

shell 复制代码
[root@server yy]# cat /opt/sc2.sh
#!/bin/bash
if [ $# -eq 0 ]; then
	echo "脚本后面必须要跟一个参数"
	exit 1
fi
if [ -f $1 ] ; then
	wc -l $1
else
	echo "$1不存在"
fi
[root@server yy]# chmod +x /opt/sc2.sh
[root@server yy]#

脚本分析:
KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲表示参数的个数,第一个判断中,# 的值如果等于0则说明脚本后面没有跟任何的参数,打印"脚本后面必须要跟一个参数",然后退出脚本。

如果后面跟了参数,则第一个判断不成立,然后进行下一个if判断。

第一个参数用$1来表示,[ -f $1 ]来判断所跟的参数是不是存在,如果存在则执行 wc -l $1, 如果不存在则执行else中的命令。

运行脚本,效果如下。

shell 复制代码
[root@server yy]# /opt/sc2.sh
脚本后面必须要跟一个参数
[root@server yy]# 

这次运行没有跟任何的参数,则提示必须要跟一个参数。

shell 复制代码
[root@server yy]# /opt/sc2.sh /etc/hostsxxx
/etc/hostsxxx不存在
[root@server yy]#

这里跟了一个不存在的文件/etc/hostsxxx,脚本提示这个文件不存在。

shell 复制代码
[root@server yy]# /opt/sc2.sh /etc/hosts
2 /etc/hosts
[root@server yy]#

这次脚本后面跟了一个存在的文件/etc/hosts,脚本会显示该文件的行数,为2行。

21.7 for循环

有时我们需要做多次重复的操作,例如,创建100个用户,创建一个用户需要两条命令:useradd和passwd。那么,创建100个用户就要重复执行100次,总共执行200条命令,那么此时我们就可以利用for循环简化操作,让系统自动帮我们重复运行即可。

for循环的语法如下。

shell 复制代码
for 变量 in  值-1  值-2  值-3  值-4 ; do
	命令 $变量
done

这里首先把值-1带给变量,执行do和done之间的命令,所有命令执行完毕之后,再把值-2赋值给变量,执行do和done之间的命令,执行完所有命令之后,再把值-3赋值给变量,以此类推,直到把所有的值都赋值给变量。

看一个简单的例子,如下所示。

shell 复制代码
[root@server yy]# for i in 1 2 3 4 ; do 此处按【Enter】键
> let i=$i+10
> echo $i
> done
11
12
13
14
[root@server yy]#

这里for后面定义了一个变量i,在in后面指定了4个值,分别是1,2,3,4。在do和done中定义了两个命令,第一个是在变量i的原有值的基础上加上10,然后打印i的值。

先把1赋值给i,此时i的值为1,执行do和done之间的命令。i加上10之后,i的值就变成了11,然后打印i,得到11,第一次循环结束。

然后把2赋值给i,此时i的值为2,执行do和done之间的命令,i加上10之后,i的值变为了12,然后打印i,得到12,第二次循环结束。

21.8 while循环

while也可以循环,while的语法如下。

shell 复制代码
while 判断 ; do
	命令1
	命令2
done

如果while后面的判断成立,则执行do和done之间的命令,在最后一个命令执行完毕之后,会回头再次判断一下while后面的判断是不是成立。如果不成立了,则跳出循环执行done后面的命令,如果成立则继续执行do和done之间的命令,就这样循环反复下去。

先看一个简单的例子,写一个脚本/opt/sc3.sh,命令如下。

shell 复制代码
[root@server yy]# cat /opt/sc3.sh
#!/bin/bash
declare -i n=1
while [ $n -le 4 ]  ; do
	echo $n
	let n=$n+1
done
[root@server yy]# chmod +x /opt/sc3.sh
[root@server yy]# 

脚本分析:

这里先通过declare -i n=1 定义了一个整数类型的变量n,初始值为1。然后进入while进行循环,先判断 n 的值是不是小于等于 4 ,如果成立,则执行 d o 和 d o n e 中的语句。一开始 n的值是不是小于等于4,如果成立,则执行do和done中的语句。 一开始 n的值是不是小于等于4,如果成立,则执行do和done中的语句。一开始n的值为1,则[ n − l e 4 ] 这个判断成立,则进入 d o 和 d o n e 之间执行命令。首先打印 n -le 4 ]这个判断成立,则进入do和done之间执行命令。首先打印 n−le4]这个判断成立,则进入do和done之间执行命令。首先打印n的值,然后在此基础上给n加上1,所以n的值变为了2,这样do和done之间的命令执行完毕了。然后再次到while后面进行判断,此时 n 的值为 2 ,依然满足小于等于 4 ,再次执行 d o 和 d o n e 之间的命令。如此反复,当 n的值为2,依然满足小于等于4,再次执行do和done之间的命令。 如此反复,当 n的值为2,依然满足小于等于4,再次执行do和done之间的命令。如此反复,当n的值最终能增长到4,然后打印,再然后加1,此时n变成了5。当$n的值变为5了之后,while后面的判断就不再成立了,此时会跳出while循环。

用while也可以用于循环一个文件的内容,用法如下。

shell 复制代码
while read aa ; do
	命令
done < file

这里read后面的变量aa是可以随意指定的,整体的意思是首先读取file的第一行内容赋值给aa,执行do和done之间的命令。然后读取file的第二行赋值给aa,执行do和done之间的命令,直到读取到file的最后一行。

有时while需要一直循环下去(死循环),语法如下。

shell 复制代码
while true ; do
	命令
done

或者

shell 复制代码
while ((1)) ; do
	命令
done
或者
while : ; do
	命令
done

下面写一个脚本,来实时判断vsftpd是否启动,如果没有启动,则将vsftpd启动,命令如下。

shell 复制代码
[root@server yy]# cat /opt/sc4.sh
#!/bin/bash
while : ; do
	systemctl is-active vsftpd &> /dev/null
	if [ $? -ne 0 ]; then
		systemctl start vsftpd
	fi
	sleep 1
done
[root@server yy]# chmod +x /opt/sc4.sh
[root@server yy]#

这里写了一个while循环,可以一直循环下去,循环中先判断vsftpd是否启动,如果启动了返回值为0,如果没有启动则返回值为非零。

下面开始根据返回值来进行判断,如果$?不等于0,说明vsftpd没有启动,则启动vsftpd服务。sleep 1的意思是暂停1s,这样就实现了每隔1s来判断一次vsftpd是否启动。

下面开始测试这个脚本,先把脚本放在后台运行,命令如下。

[root@server yy]# /opt/sc4.sh &

[1] 48786

[root@server yy]#

测试当前vsftpd的状态,命令如下。

[root@server yy]# systemctl is-active vsftpd

active

[root@server yy]#

关闭vsftpd服务之后,再次检测vsftpd的状态,命令如下。

[root@server yy]# systemctl stop vsftpd

[root@server yy]# systemctl is-active vsftpd

active

[root@server yy]#

可以看到,vsftpd仍然是启动的,说明我们的脚本生效了。

作业

1.请写一个脚本count.sh,用来统计文件的行数,要求:

(1)如果后面没有参数,则报错"必须要跟一个文件",然后退出脚本

(2)如果跟的不是普通文件或不存在的文件,则提示"必须是一个普通文件",并退出脚本

(3)如果脚本正常运行,输入格式为:

行数 文件名

2.写一个脚本/opt/ip.sh,用于获取本机ens160的IP地址,要求:

(1)只有root才能执行此脚本,其他用户执行脚本时提示"只有root才能执行此脚本",并退出脚本。

(2)如果脚本正确执行,则在屏幕上显示一个IP地址。

相关推荐
致奋斗的我们1 个月前
RHCE的学习(22)
linux·服务器·学习·shell·redhat·rhce·rhcsa
致奋斗的我们1 个月前
RHCE的学习(21)
linux·学习·shell·redhat·rhce·rhcsa
致奋斗的我们1 个月前
RHCE的学习(11)
linux·网络·学习·redhat·rhce·rhcsa
正儿八经的地球人2 个月前
RHCE【SELinux】
linux·运维·网络·rhce
致奋斗的我们2 个月前
RHCE的学习(7)
linux·服务器·网络·学习·redhat·rhce·rhcsa
致奋斗的我们2 个月前
RHCE的学习(3)
linux·运维·服务器·开发语言·学习·rhce·rhcsa
正儿八经的地球人2 个月前
RHCE【时间服务器】
运维·服务器·网络·rhce
咕噜Yuki06093 个月前
博睿谷IT认证-订阅试学习
linux·学习·redhat·rhce·红帽认证·ocm认证
疯狂的rabbit.4 个月前
企业高性能web服务器知识点合集
linux·运维·服务器·前端·nginx·web·rhce
疯狂的rabbit.5 个月前
系统服务综合案例
linux·运维·服务器·rhce