用bash写脚本

本章主要介绍如何使用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个字。

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

1 通配符

通配符一般用在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并在目录中创建如下几个测试文件,命令如下。

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

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

复制代码
[root@redhat8 xx]# ls [a-z][1-9]*
f1aa

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

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

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

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

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

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

可以看到,首字符是大写字母的文件列出来了,首字符是小写字母的文件有的列出来了, 有的没有列出来,所以a-zA-Z有时并不精确。如果要更精确,可以用如下元字符。

(1)\[:upper:]:纯大写。

(2)\[:lower:]:小写。

(3)\[:alpha:]:字母。

(4)\[:alnum::字母和数字。

(5)\[:digit:]:数字。

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

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

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

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

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

复制代码
[root@redhat8 ~]# yum list vsftpd*
正在更新 Subscription Management 软件仓库。
无法读取客户身份

本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。

上次元数据过期检查:21:39:27 前,执行于 2023年12月14日 星期四 12时09分33秒。
可安装的软件包
vsftpd.x86_64                                            3.0.3-34.el8                                            aa

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

复制代码
[root@redhat8 ~]# touch vsftpdxxx

然后再执行yum list vsftpd*命令,命令如下。

复制代码
[root@redhat8 ~]# yum list vsftpd*
正在更新 Subscription Management 软件仓库。
无法读取客户身份

本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。

上次元数据过期检查:21:40:00 前,执行于 2023年12月14日 星期四 12时09分33秒。
错误:没有匹配的软件包可以列出

此处显示没有匹配的包,为什么呢?因为yum是 bash的一个子进程,vsftpd在 bash中首先被解析成了vsftpdxxx,然后再经过yum。所以,本质上执行的是yum list vsftpdxx命令,而yum源中是没有vsftpdxxx 这个包的,所以报错。

为了防止 bash 对这里的*进行解析,可以加上转义符"\",所以下面的命令是正确的。

复制代码
[root@redhat8 ~]# yum list vsftpd\*
正在更新 Subscription Management 软件仓库。
无法读取客户身份

本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。

上次元数据过期检查:21:40:11 前,执行于 2023年12月14日 星期四 12时09分33秒。
可安装的软件包
vsftpd.x86_64                                            3.0.3-34.el8           
2 变量

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

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

2.1 本地变量

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

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

(1)变量名可以包含_、数字、大小写字母,但不能以数字开头。

(2)"="两边不要有空格。

(3)"值"如果含有空格,要使用单引号''或双引号""引起来。

(4)定义变量时,变量名前是不需要加的,引用变量时需要在变量名前加

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

复制代码
[root@redhat8 ~]# cd
[root@redhat8 ~]# mkdir yy ; cd yy

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

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

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

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

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

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

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

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

这里的错误是因为"值"部分有空格没有用引号引起来。

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

这里正确地定义了一个变量。

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

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

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

复制代码
[root@redhat8 yy]# echo $aa
123
[root@redhat8 yy]# 
[root@redhat8 yy]# echo $$
4716

当前shell的PID是4716。下面打开一个子shell。

复制代码
[root@redhat8 yy]# bash
[root@redhat8 yy]# echo $$
4947

这个子shell 的PID是4947。

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

[root@redhat8 yy]# 

可以看到,没有aa变量。

复制代码
[root@redhat8 yy]# exit
exit
[root@redhat8 yy]# echo $$
4716
[root@redhat8 yy]# echo $aa
123

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

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

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

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

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

方法2:通过read命令来获取变量。

read的用法如下。

复制代码
read ‐p "提示信息" 变量

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

复制代码
[root@redhat8 yy]# read -p "请输入您的名字" aa
请输入您的名字tom
[root@redhat8 yy]# echo $aa
tom

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

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

2.2 环境变量

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

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

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

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

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

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

复制代码
[root@redhat8 yy]# echo $$
4716
[root@redhat8 yy]# echo $bb
123

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

复制代码
[root@redhat8 yy]# bash
[root@redhat8 yy]# echo $$
5126
[root@redhat8 yy]# echo $bb
123

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

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

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

复制代码
[root@redhat8 yy]# echo $$
4716
[root@redhat8 yy]# echo $bb
123

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

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

(1)UID:表示当前用户的uid。

(2)USER:表示当前用户名。

(3)HOME:表示当前用户的家目录

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

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

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

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

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

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

复制代码
[tom@redhat8 yy]$ echo $PATH
/home/tom/.local/bin:/home/tom/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

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

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

2.3 位置变量和预定义变量

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

$0:表示脚本的名称。

$1:表示第1个参数。

$2:表示第2个参数。

......

${10}:表示第10个参数。

......

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

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

$#:表示参数的个数。

$*:表示所有的参数。

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

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

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

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

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

例2:运行如下命令。

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

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

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

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

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

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

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

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

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

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

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

3 返回值

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

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

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

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

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

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

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

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

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

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

4数值运算

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

(1)+:表示加。

(2)-:表示减。

(3)*:表示乘。

(4)/:表示除。

(5)**:表示次方。

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

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

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

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

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

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

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

这里aa的值就是为3。 下面来看不使用let的情况,命令如下。

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

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

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

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

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

以上表达式不能求得小数,如果要得到小数需要使用 bc 命令,用法如下。

复制代码
echo "scale=N ; 算法 | bc"

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

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

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

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

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

复制代码
[root@redhat8 yy]# echo "scale=3 ; 7/6" | bc
1.166
5 比较、对比、判断

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

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

5.1数字的比较

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

(1)-eq:相等。

(2)-ne:不相等。

(3)-gt:大于。

(4)-ge:大于等于。

(5)-lt:小于。

(6)-le:小于等于。

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

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

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

1是不能等于2的,所以判断不成立,返回值为非零。注意中括号和比较符两边的空格。

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

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

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

5.2 字待串的比较

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

(1)==:相同。

(2)!=:不相同。

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

练习1:定义一个变量aa=tom,然后做判断,命令如下

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

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

练习2:在判断中匹配通配符,命令如下。

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

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

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

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

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

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

注意

(1)==后面跟的是通配符,如果想跟正则表达式,比较符就不能使用==了,要换成=~。 (2)一定要注意中括号和比较符两边的空格。

5.3 属性的判断

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

(1)-r:具备读权限。

(2)-w:具备写权限。

(3)-x:具备可执行权限。

注意

以上三个属性,不管是出现在u、g还是o上,只要有就算判断成立。

()-d:一个目录。

()-l:一个软链接。

()-f:一个普通文件,且要存在。

()-e:不管什么类型的文件,只要存在就算判断成立。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5.4 使用连接符

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

先看一下使用&&作为连接符,用法如下。

复制代码
判断1 && 判断2

只有两个判断都为真(返回值为0),整体才为真,只要有一个为假,整体就为假。判断1如果为假,判断2还有必要执行吗?没有,因为整体已经确定为假了。判断1为真,整体是真是假在于判断2,所以判断2肯定是要执行的。

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

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

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

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

下面看使用||作为连接符,用法如下。

复制代码
判断1 || 判断2

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

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

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

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

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

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

这里有两个判断,第一个判断是1大于等于2,第二个判断是2大于等于3,这两个判断都为假,所以整个判断为假,返回值为非零。

6 if判断语句

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

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

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

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

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

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

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

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

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

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

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

脚本分析如下。

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

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

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

如果是 root执行此脚本,则判断不成立,直接执行f后面的命令。

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

复制代码
[root@redhat8 opt]# /opt/sc1.sh
hello root

使用blab用户执行此脚本的结果如下。

复制代码
[tom@redhat8 opt]$ /opt/sc1.sh 
echo只有root才能执行此脚本

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

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

复制代码
[root@redhat8 opt]# 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

脚本分析如下。

#表示参数的个数,第一个判断中,#的值如果等于0,则说明脚本后面没有跟任何参数,打印"脚本后面必须跟一个参数",然后退出脚本。

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

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

运行脚本,效果如下。

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

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

复制代码
[root@redhat8 opt]# /opt/sc2.sh /etc/hostxxx
/etc/hostxxx不存在

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

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

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

7 for循环语句

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

for循环的语法如下。

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

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

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

复制代码
[root@redhat8 opt]# for i in 1 2 3 4 ; do
> let i=$i+10
> echo $i
> done
11
12
13
14

这里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,第二次循环结束。

8 while 循环语句

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

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

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

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

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

脚本分析如下。

这里先通过declare -i n=1定义了一个整数类型的变量n,初始值为1。然后进入while进行循环,先判断$n的值是不是小于等于4,如果成立,则执行do和 done之间的命令。

一开始n的值为1,\[ n -le 4 ]这个判断成立,则进人 do和done之间执行命令。首先打印Sn的值,然后在此基础上给n 加上1,所以n的值变为了2,这样do和done之间的命令就执行完成了。然后再次到while后面进行判断,此时$n的值为2,依然满足小于等于4,再次执行do 和 done之间的命令。

如此反复,当$n的值最终能增加到4时打印,然后加1,此时n的值变为了5。当Sn的值变 为5之后,while后面的判断就不再成立了,此时会跳出 while循环。

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

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

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

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

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

bash 复制代码
while ((1)) ; do
命令
done

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

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

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

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

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

cpp 复制代码
[root@redhat8 opt]# /opt/sc4.sh &
[1] 38619

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

cs 复制代码
[root@redhat8 opt]# systemctl is‐active vsftpd
active

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

cs 复制代码
[root@redhat8 opt]# systemctl stop vsftpd
[root@redhat8 opt]# systemctl is‐active vsftpd
active

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

相关推荐
A小辣椒18 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言