如何使用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个字。 

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


通配符 

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

  • [ ]:匹配一个字符,匹配的是出现在中括号中的字符。
  •  [abc]:匹配一个字符,且只能是a或b或c。
  • [a-z]:“-”有特殊意义,表示“到”的意思,这里表示a~z,即匹配任一字母。
  • [0-9]:表示匹配任一数字。

如果想去除含有特殊意义的字符,前面加“\”表示转义,即去除此字符的特殊意义。

  • [a\-z]:这里的“-”就没有“到”的意思了,匹配的是“a”或“-”或“z”这三个 中的一个。

如果想表示“除了”的意思,则在第一个中括号后面加“!”或“^”。

  • [!a-z]、[^a-z]:表示除字母外的其他字符。
  • ?:表示一个任意字符,这里强调是一个,不是0个也不是多个,但不能匹配表示隐藏 文件的点。
  • *:表示任意多个任意字符,可以是0个,也可以是1个或多个,但不能匹配表示隐藏 文件的点。

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

[root@rhel03 ~]# mkdir xx
[root@rhel03 ~]# cd xx
[root@rhel03 xx]# touch 1_aa aa11 Aa11 _aaa aa.txt f1aa u_12
[root@rhel03 xx]# 

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

[root@rhel03 xx]# ls [a-z][0-9]*
f1aa
[root@rhel03 xx]#

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

[root@rhel03 xx]# ls [a-z][^0-9]*
aa11  Aa11  aa.txt  u_12
[root@rhel03 xx]#

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

[root@rhel03 xx]# ls [^a-z][^0-9]*
1_aa  _aaa
[root@rhel03 xx]#

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

[root@rhel03 xx]# ls [A-Z][!0-9]*
Aa11  u_12
[root@rhel03 xx]#

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

  • [[:upper:]]:纯大写。
  • [[:lower:]]:小写。
  • [[:alpha:]]:字母。
  • [[:alnum:]:字母和数字。
  • [[:digit:]]:数字。 

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

[root@rhel03 xx]# ls [[:lower:]][0-9]*
f1aa
[root@rhel03 xx]# 

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

[root@rhel03 xx]# ls [[:upper:]][[:alnum:]]*
Aa11
[root@rhel03 xx]#

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

[root@rhel03 ~]# yum list vsftpd*
正在更新 Subscription Management 软件仓库。
无法读取客户身份本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。aa                                                                                                                       1.2 MB/s | 3.2 kB     00:00    
bb                                                                                                                       2.3 MB/s | 2.8 kB     00:00    
可安装的软件包
vsftpd.x86_64                                                               3.0.3-34.el8                                                               aa
[root@rhel03 ~]# 

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

[root@rhel03 ~]# touch vsftpdxxx
[root@rhel03 ~]#

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

[root@rhel03 ~]# yum list vsftpd*
正在更新 Subscription Management 软件仓库。
无法读取客户身份本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。上次元数据过期检查:0:02:14 前,执行于 2023年12月10日 星期日 14时55分36秒。
错误:没有匹配的软件包可以列出
[root@rhel03 ~]# 

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

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

[root@rhel03 ~]# yum list vsftpd\*
正在更新 Subscription Management 软件仓库。
无法读取客户身份本系统尚未在权利服务器中注册。可使用 subscription-manager 进行注册。上次元数据过期检查:0:03:11 前,执行于 2023年12月10日 星期日 14时55分36秒。
可安装的软件包
vsftpd.x86_64                                                               3.0.3-34.el8                                                               aa
[root@rhel03 ~]#

变量 

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

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

(1)本地变量 

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

  • 变量名=值 

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

  1. 变量名可以包含_、数字、大小写字母,但不能以数字开头。
  2. “=”两边不要有空格。
  3. “值”如果含有空格,要使用单引号''或双引号""引起来。
  4. “值”如果含有空格,要使用单引号''或双引号""引起来。 

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

[root@rhel03 ~]# mkdir yy ; cd yy
[root@rhel03 yy]#

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

[root@rhel03 yy]# 1aa=123
bash: 1aa=123: 未找到命令...
[root@rhel03 yy]# 

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

[root@rhel03 yy]# aa-1=123
bash: aa-1=123: 未找到命令...
[root@rhel03 yy]# 

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

[root@rhel03 yy]# aa =123
bash: aa: 未找到命令...
[root@rhel03 yy]# 

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

[root@rhel03 yy]# aa=1 2
bash: 2: 未找到命令...
[root@rhel03 yy]#

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

[root@rhel03 yy]# aa=123
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]#

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

[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]#

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

[root@rhel03 yy]# bash
[root@rhel03 yy]# echo $$
2452
[root@rhel03 yy]#

这个子shell 的PID是2452。

[root@rhel03 yy]# echo $aa[root@rhel03 yy]#

可以看到,没有aa变量。 

[root@rhel03 yy]# exit
exit
[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $aa
123
[root@rhel03 yy]# 

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

 

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

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

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

[root@rhel03 yy]# ip=$(ifconfig ens160 | awk '/inet /{print $2}')
[root@rhel03 yy]# echo $ip
192.168.23.33
[root@rhel03 yy]#

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

read的用法如下。

  • read ‐p "提示信息" 变量 

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

[root@rhel03 yy]# read -p "请输入您的名字:" aa
请输入您的名字:tom
[root@rhel03 yy]# echo $aa
tom
[root@rhel03 yy]# 

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

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


(2)环境变量 

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

[root@rhel03 yy]# export bb=123
[root@rhel03 yy]#

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

[root@rhel03 yy]# bb=123
[root@rhel03 yy]# export bb
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]#

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

[root@rhel03 yy]# bash
[root@rhel03 yy]# echo $$
2555
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]# 

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

[root@rhel03 yy]# export bb=456
[root@rhel03 yy]# exit
exit
[root@rhel03 yy]#

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

[root@rhel03 yy]# echo $$
2092
[root@rhel03 yy]# echo $bb
123
[root@rhel03 yy]# 

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

 

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

  1. UID:表示当前用户的uid。
  2. USER:表示当前用户名。
  3. HOME:表示当前用户的家目录。

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

[root@rhel03 yy]# echo $UID
0
[root@rhel03 yy]# echo $USER
root
[root@rhel03 yy]# echo $HOME
/root
[root@rhel03 yy]#

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

[root@rhel03 yy]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@rhel03 yy]#

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

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

[tom@rhel03 ~]$ echo $PATH
/home/tom/.local/bin:/home/tom/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
[tom@rhel03 ~]$ 

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

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


(3)位置变量和预定义变量 

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

  • $0:表示脚本的名称。
  • $1:表示第1个参数。
  • $2:表示第2个参数。
  • ......... 
  • ${10}:表示第10个参数。 

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

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

  • $#:表示参数的个数。
  • $*:表示所有的参数。 

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

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

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

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

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

例2:运行如下命令。

[root@rhel03 yy]# set a b c d e f g h i j k
[root@rhel03 yy]#

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

[root@rhel03 yy]# echo $1
a
[root@rhel03 yy]# echo $9
i
[root@rhel03 yy]# 

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

[root@rhel03 yy]# echo $10
a0
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# echo ${10}
j
[root@rhel03 yy]#

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

[root@rhel03 yy]# xx=tom
[root@rhel03 yy]# echo "my name is $xx"
my name is tom
[root@rhel03 yy]# echo 'my name is $xx'
my name is $xx
[root@rhel03 yy]#

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


返回值 

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

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

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

[root@rhel03 yy]# xxxx
bash: xxxx: 未找到命令...
[root@rhel03 yy]# echo $?
127
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#

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

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

[root@rhel03 yy]# grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

[root@rhel03 yy]# grep ^rootxxx /etc/passwd
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#

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


数值运算 

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

  • +:表示加。
  • -:表示减。
  • *:表示乘。
  • /:表示除。
  • **:表示次方。 

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

[root@rhel03 yy]# echo $((2+3))
5
[root@rhel03 yy]# echo $((2*3))
6
[root@rhel03 yy]# echo $((2**3))
8
[root@rhel03 yy]# echo $[2**3]
8
[root@rhel03 yy]# 

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

[root@rhel03 yy]# echo 2**3
2**3
[root@rhel03 yy]#

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

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

[root@rhel03 yy]# let aa=1+2
[root@rhel03 yy]# echo $aa
3
[root@rhel03 yy]# 

这里aa的值就是为3。

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

[root@rhel03 yy]# aa=1+2
[root@rhel03 yy]# echo $aa
1+2
[root@rhel03 yy]#

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

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

[root@rhel03 yy]# declare -i aa
[root@rhel03 yy]# aa=1+2
[root@rhel03 yy]# echo $aa
3
[root@rhel03 yy]# 

首先declare -i aa把aa定义为一个整数,所以1+2等于3,然后赋值给aa.所以aa的值为3。 以上表达式不能求得小数,如果要得到小数需要使用 bc 命令,用法如下。 

  • echo "scale=N ; 算法 | bc" 

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

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

[root@rhel03 yy]# echo "scale=3 ; 2/3" | bc
.666
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# echo "scale=3 ; 7/6" | bc
1.166
[root@rhel03 yy]# 

比较、对比、判断 

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

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


(1)数字的比较 

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

  • -eq:相等。
  • -ne:不相等。
  • -gt:大于。
  • -ge:大于等于。
  • -lt:小于。
  • -le:小于等于。 

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

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

[root@rhel03 yy]# [ 1 -eq 2 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# [ 1 -ne 2 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#

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


(2)字待串的比较 

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

  • ==:相同。
  • !=:不相同。 

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

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

[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [ $bb == tom ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [ $bb == to? ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]# 

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

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

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

[root@rhel03 yy]# bb=tom
[root@rhel03 yy]# [[ $bb == to? ]]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

注意:

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

(3)属性的判断 

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

  • -r:具备读权限。
  • -w:具备写权限。
  • -x:具备可执行权限。

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

  • -d:一个目录。
  • -l:一个软链接。
  • -f:一个普通文件,且要存在。
  • -e:不管什么类型的文件,只要存在就算判断成立。 

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

[root@rhel03 yy]# ls -l /etc/hosts
-rw-r--r--. 1 root root 185 12月  4 12:17 /etc/hosts
[root@rhel03 yy]# [ -r /etc/hosts ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]#

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

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

[root@rhel03 yy]# [ -x /etc/hosts ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]# 

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

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

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

[root@rhel03 yy]# [ ! -x /etc/hosts ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# [ -f /etc/ ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]# 

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

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

[root@rhel03 yy]# [ -e /etc/ ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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


(4)使用连接符 

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

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

  • 判断1 && 判断2 

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

[root@rhel03 yy]# [ 1 -eq 2 ] && [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]# 

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

[root@rhel03 yy]# [ 1 -le 2 ] && [ 2 -le 3 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

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

  • 判断1 || 判断2 

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

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

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

[root@rhel03 yy]# [ 1 -le 2 ] || [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
0
[root@rhel03 yy]# 

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

[root@rhel03 yy]# [ 1 -ge 2 ] || [ 2 -ge 3 ]
[root@rhel03 yy]# echo $?
1
[root@rhel03 yy]#

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


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。

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

[root@rhel03 opt]# cat scl.sh 
#/bin/bash
if [ $UID -ne 0 ]; thenecho "只有root才能执行此脚本"exit 1
fiecho "hello root"
[root@rhel03 opt]# chmod +x /opt/scl.sh 
[root@rhel03 opt]# 

脚本分析如下。

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

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

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

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

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

[root@rhel03 opt]# ./scl.sh 
hello root
[root@rhel03 opt]# 

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

[tom@rhel03 opt]$ ./scl.sh 
只有root才能执行此脚本
[tom@rhel03 opt]$ 

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

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

[root@rhel03 opt]# cat sc2.sh 
#!/bin/bash
if [ $# -eq 0 ] ; thenecho "脚本后面必须跟一个参数"exit 1
fi
if [ -f $1 ] ; thenwc -l $1
elseecho "$1不存在"
fi
[root@rhel03 opt]# chmod +x /opt/sc2.sh
[root@rhel03 opt]# 

脚本分析如下。

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

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

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

运行脚本,效果如下。

[root@rhel03 opt]# ./sc2.sh 
脚本后面必须跟一个参数
[root@rhel03 opt]# 

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

[root@rhel03 opt]# ./sc2.sh /etc/hostsxxx
/etc/hostsxxx不存在
[root@rhel03 opt]# 

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

[root@rhel03 opt]# ./sc2.sh /etc/hosts
3 /etc/hosts
[root@rhel03 opt]# 

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


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@rhel03 opt]# for i in 1 2 3 4 ; do
> let i=$i+10
> echo $i
> done
11
12
13
14
[root@rhel03 opt]# 

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


while 循环语句 

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

while 判断 ; do

命令1

命令2

done 

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

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

[root@rhel03 opt]# cat sc3.sh 
#!/bin/bash
declare -i n=1
while [ $n -le 4 ] ; doecho $nlet n=$n+1
done
[root@rhel03 opt]# chmod +x sc3.sh 
[root@rhel03 opt]#

脚本分析如下。

  这里先通过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也可以用于循环一个文件的内容,用法如下。

 while read aa ; do

命令

done < file

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

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

  • while true ; do
  • 命令
  • done

或者

  • while ((1)) ; do
  • 命令
  • done

或者

  • while : ; do
  • 命令
  • done

 

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

[root@rhel03 opt]# cat sc4.sh 
#!/bin/bash
while : ; dosystemctl is-active vsftpd &> /dev/null
if [ $? -ne 0 ]; thensystemctl start vsftpd
fisleep 1
done
[root@rhel03 opt]# chmod +x /opt/sc4.sh 
[root@rhel03 opt]# 

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

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

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

[root@rhel03 opt]# ./sc4.sh  &
[2] 4079
[root@rhel03 opt]#

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

[root@rhel03 opt]# systemctl is-active vsftpd
active
[root@rhel03 opt]# 

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

[root@rhel03 opt]# systemctl stop vsftpd
[root@rhel03 opt]# systemctl is-active vsftpd
active
[root@rhel03 opt]# 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/217009.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在做题中学习(33):只出现一次的数字 II

137. 只出现一次的数字 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 1.首先想到出现三次的数&#xff0c;它们仨的任意一位都是相同的&#xff08;1/0&#xff09; 2.可以发现出现三次的数的某一位和a某一位在所有情况下%3最后的结果都和a的那一位相同&…

【PyTorch】卷积神经网络

文章目录 1. 理论介绍1.1. 从全连接层到卷积层1.1.1. 背景1.1.2. 从全连接层推导出卷积层 1.2. 卷积层1.2.1. 图像卷积1.2.2. 填充和步幅1.2.3. 多通道 1.3. 池化层&#xff08;又称汇聚层&#xff09;1.3.1. 背景1.3.2. 池化运算1.3.3. 填充和步幅1.3.4. 多通道 1.4. 卷积神经…

抖店商品卡流量怎么做?附实操玩法解析及案例分享!

我是电商珠珠 在抖店中&#xff0c;我们会看到一些店铺从来都不直播&#xff0c;官方账号的作品很少&#xff0c;但是他的店铺GMV依旧过万。 其实这就是抖音的货架电商所带来的优势&#xff0c;抖音现在主要由两部分组成&#xff0c;一部分是内容场&#xff0c;就是咱们平时刷…

SpringBoot整合MongoDB详解

SpringBoot——整合MongoDB详解 注意&#xff1a; MongoDB默认是本机访问 需要开启远程访问 window修改如下 linux 修改如下 /usr/local/mongodb4/mongodb.conf 详细内容 dbpath/data/mongodb4/mongo #数据文件保存地址 logpath/data/mongodb4/log/mongod.log #日志保存地址…

架构设计系列之基础:初探软件架构设计

11 月开始突发奇想&#xff0c;想把自己在公司内部做的技术培训、平时的技术总结等等的内容分享出来&#xff0c;于是就开通了一个 Wechat 订阅号&#xff08;灸哥漫谈&#xff09;&#xff0c;开始同步发送内容。 今天&#xff08;12 月 10 日&#xff09;也同步在 CSDN 上开通…

mysql字段设计规范:使用unsigned(无符号的)存储非负值

如果一个字段存储的是数值&#xff0c;并且是非负数&#xff0c;要设置为unsigned&#xff08;无符号的&#xff09;。 例如&#xff1a; 备注&#xff1a;对于类型是 FLOAT、 DOUBLE和 DECIMAL的&#xff0c;UNSIGNED属性已经废弃了&#xff0c;可能在mysql的未来某个版本去…

AI隆重软件,AI原创文章隆重软件

随着信息量的急剧增加&#xff0c;许多写作者、网站管理员和内容创作者们纷纷感受到了文章降重的压力。原始文本的降重&#xff0c;需要保留关键信息的同时避免重复&#xff0c;这是一项既繁琐又耗时的任务。 改写软件的批量降重功能 147SEO改写软件在降重领域的卓越表现主要体…

常见SSL证书都有哪些格式

常见Web服务软件 常见的Web服务软件&#xff0c;通常都基于OpenSSL和Java两种基础密码库。 Tomcat、Weblogic、JBoss等Web服务软件&#xff0c;一般使用Java提供的密码库。通过Java Development Kit&#xff08;JDK&#xff09;工具包中的Keytool工具&#xff0c;生成Java Ke…

android studio 按键点击事件的实现方法

一、onClick属性&#xff1a; 1&#xff09;、在activity_main.xml中设置button的onClick属性&#xff1a; <Buttonandroid:id"id/button"android:layout_width"wrap_content"android:layout_height"wrap_content"android:text"开灯&q…

SpringCloud系列(三)| 集成Nacos注册中心

好了万事具备&#xff0c;接下来我们就开始搭建我们SpringCloud项目。本章节我们先来搭建我们SpringCloud项目框架&#xff0c;然后开发两个服务&#xff0c;把他们注册到Nacos注册中心中。上一章节我们已经介绍了Nacos是可以作为注册中心和配置中心的&#xff0c;这一章节我们…

Linux centos8安装JDK1.8、tomcat

一、安装jdk 1.如果之前安装过jdk&#xff0c;先卸载掉旧的 rpm -qa | grep -i jdk 2.检查yum中有没有java1.8的包 yum list java-1.8* 3.yum安装jdk yum install java-1.8.0-openjdk* -y 4.验证 二、安装tomcat Index of /tomcat 可以在这里选择你想要安装的tomcat版本…

【unity实战】一个通用的FPS枪支不同武器射击控制脚本

文章目录 前言模型素材文章用到的粒子火光特效射击效果换弹瞄准开枪抖动效果设置显示文本最终代码不同武器射击效果1. 手枪2. 机枪3. 狙击枪4. 霰弹枪5. 加特林 其他感谢完结 前言 实现FPS枪支不同武器效果&#xff0c;比如手枪&#xff0c;喷子&#xff0c;狙击枪&#xff0c…

ISCTF2023 Reverse方向 WP

文章目录 ReversecrackmeEasyRebabyReeasy_z3FloweyRSAeasy_flower_teamfx_rez3_revengeWHERE Reverse crackme 、 加了UPX壳&#xff0c;可以看到EP Section处UPX标识被修改了 用WinHex修改 之后UPX脱壳 得到flag。 EasyRe 逆向一下&#xff0c;先逆序&#xff0c;再做一些…

机器人行业数据闭环实践:从对象存储到 JuiceFS

JuiceFS 社区聚集了来自各行各业的前沿科技用户。本次分享的案例来源于刻行&#xff0c;一家商用服务机器人领域科技企业。 商用服务机器人指的是我们日常生活中常见的清洁机器人、送餐机器人、仓库机器人等。刻行采用 JuiceFS 来弥补对象存储性能不足等问题。 值得一提的是&am…

godot 报错Unable to initialize Vulkan video driver解决

版本 godot 4.2.1 现象 godot4.2.1 默认使用vulkan驱动&#xff0c;如果再不支持vulkan驱动的主机上&#xff0c;进入引擎编辑器将报错如下 解决 启动参数添加 –rendering-driver opengl3 即可进入引擎编辑器 此时运行项目仍然会报错无法初始化驱动 在项目设置中配置编…

编译Sqlite3记录

下载源文件&#xff1a; 下载地址&#xff1a;SQLite Download Page 打开QtCreator创建新的工程&#xff0c;选择纯C工程&#xff0c;将main.c删除&#xff0c;将下载的源码解压后的文件复制到并添加到工程中&#xff0c;其中的文件包括&#xff1a;sqlite3ext.h、sqlite3.h、…

Android多进程和跨进程通讯方式

前言 我们经常开发过程中经常会听到线程和进程&#xff0c;在讲述Android进程多进程前我打算先简单梳理一下这俩者。 了解什么是进程与线程 进程&#xff1a; 系统中正在运行的一个应用程序&#xff0c;某个程序一旦运行就是一个进程&#xff0c;是资源分配的最小单位&#…

自动化测试 (二) Web自动化测试原理

目前市面上有很多Web UI自动化测试框架&#xff0c;比如WatiN, Selinimu,WebDriver&#xff0c;还有VS2010中的Coded UI等等. 这些框架都可以操作Web中的控件&#xff0c;模拟用户输入&#xff0c;点击等操作&#xff0c;实现Web自动化测试。其实这些工具的原理都一样&#xf…

element-ui样式(一)

1.去掉表格横线 HTML表格标签&#xff1a; table&#xff1a;定义表格&#xff0c;生成的表格在一对<table></table>中&#xff1b; <th>&#xff1a;定义表格的表头&#xff0c;一般是表头中的内容会被加黑&#xff08;table head&#xff09;&#xff1b;…

MySQL 系列:注意 ORDER 和 LIMIT 联合使用的陷阱

文章目录 前言背后的原因ORDER BY 排序列存在相同值时返回顺序是不固定的LIMIT 和 ORDER BY 联合使用时的行为ORDER BY 或 GROUP BY 和 LIMIT 联合使用优化器默认使用有序索引 如何解决其它说明个人简介 前言 不知道大家在在分页查询中有没有遇到过这个问题&#xff0c;分页查…