四、Shell脚本语言的运算
4.1 算数运算
shell支持算术运算,但只支持整数,不支持小数
4.2 Bash中的算术运算
-- + 加法运算 -- - 减法运算 -- * 乘法运算 -- / 除法运算 -- % 取模,即取余数 -- ** 乘方 #乘法符号在有些场景需要转义
4.2 实现算术运算
let var=算术表达式
var=$[算术表达式]
var=$((算术表达式))
var=$(expr arg1 arg2 arg3 ...)
declare -i var = 数值
echo '算术表达式' | bc (支持浮点数)
4.3 增强型赋值:
+= i+=10 <==> i=1+10 -= i-=j <==> i=i-j *= /= %= ++ i++,++i <==> i=i+1 (自增) -- i--,--i <==> i=i-1 (自减)
实例:
bash
[root@localhost ~]# let var+=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var++
[root@localhost ~]# echo $var
2
[root@localhost ~]# let var-=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var--
[root@localhost ~]# echo $var
0
i++ 与 ++i的区别:
i++ 先赋值再运算
++i 先运算再赋值
bash
[root@localhost ~]# unset i j ;i=1;let j=i++;echo "i=$i,j=$j"
i=2,j=1
[root@localhost ~]# unset i j ;i=1;let j=++i;echo "i=$i,j=$j"
i=2,j=2
[root@localhost ~]#
实例:鸡兔同笼问题
(1)
bash
[root@localhost ~]# vim xiaojiji.sh
#!/bin/bash
HEAD=35
FOOT=94
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHICKEN=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为:"$CHICKEN
[root@localhost ~]# chmod +x xiaojiji.sh
[root@localhost ~]# ./xiaojiji.sh
兔子的数量为:12
鸡的数量为:23
(2)在脚本中写入变量,让用户在命令行写入需要计算的数值
bash
[root@localhost ~]# vim xiaojiji.sh
#!/bin/bash
HEAD=$1
FOOT=$2
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHICKEN=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为:"$CHICKEN
[root@localhost ~]# ./xiaojiji.sh 30 80
兔子的数量为:10
鸡的数量为:25
4.2 逻辑运算(了解,不用掌握)
True用数字表示1,False用数字表示0
-
与:&
1 与 1 = 1 1 与 0 = 0 0 与 1 = 0 0 与 0 = 0
-
或:|
1 或 1 = 1 1 或 0 = 1 0 或 1 = 1 0 或 0 = 0
-
非:!
!1 = 0 !True=False !0 = 1 !False=True
-
异或:^
#异或的两个值,相同为假,不同为真 1 ^ 1 =0 1 ^ 0 =1 0 ^ 1 =1 0 ^ 0 =0
条件表达式
条件测试命令
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便在条件性环境下进行执行。
-
若真,则状态码变量$?返回0
-
若假,则状态码变量$?返回1
扩展: [ ] 与 [[ ]] 的区别
区别1:
\]是符合POSIX标准的测试语句,兼容性强,几乎可以运行在所有的Shell解释器中 \[\[ \]\]仅可运行在特定的几个Shell解释器中(如Bash) #### 区别2: \[\[ \]\] 里面支持数学类型的操作数。例如:\< \>。但不支持 =号。 ```bash [root@localhost ~]# [[ 2 >= 1 ]] -bash: 条件表达式中有语法错误 [root@localhost ~]# [[ 2 = 1 ]] -bash: 未预期的记号 "2" 附近有语法错误 [root@localhost ~]# [[ 2 > 1 ]] [root@localhost ~]# echo $? 0 ``` \[\]是不能用这样的符号的例如 \< \>。 ```bash [root@localhost ~]# [ 1 > 2 ] [root@localhost ~]# echo $? 0 [root@localhost ~]# [ 2 > 1 ] [root@localhost ~]# echo $? 0 ``` ((\<))在两个括号中间可以不用对内容进行空格,并且可以执行\<和\>号 ```bash [root@localhost ~]# ((2>1)) [root@localhost ~]# echo $? 0 [root@localhost ~]# ``` =\~ (等效于包含什么什么) ```bash [root@localhost ~]# [[ "hello" =~ "p" ]] [root@localhost ~]# echo $? 1 [root@localhost ~]# [[ "hello" =~ "h" ]] [root@localhost ~]# echo $? 0 ``` #### 区别3: 在\[ \]中使用 -a 表示逻辑与(两个都得满足) 和 -o 表示逻辑或 (满足其中一个就行)。 \[\[ \]\]使用 \&\& 逻辑与((前一个执行成功 后一个才执行),\|\| 表示 逻辑或(前一个执行失败 后一个才执行)。 \[\[ \]\]不支持-a #### 区别4: 在\[ \]中==是字符匹配(精确的) \[\[ \]\]中==是模式匹配(模糊的,可能是某一些) **区别5:** \[ \]不支持正则匹配, \[\[ \]\]支持用=\~(等效于包含什么什么)进行正则匹配 #### 区别6: \[ \]仅在部分Shell中支持用() 进行分组,\[\[ \]\]均支持 #### 区别7: 在\[ \]中如果变量没有定义,那么需要用双引号引起来(表示字符串) 在\[\[ \]\]中不需要 #### 字符串(sting),简写的时候写str 定义:1.啥也不写,2.单引号,3.双引号。 ```bash [root@localhost ~]# str1=hello [root@localhost ~]# echo $str1 hello [root@localhost ~]# str2='hello' [root@localhost ~]# echo $str2 hello [root@localhost ~]# str3="hello" [root@localhost ~]# echo $str3 hello [root@localhost ~]# ``` ' ' 引用不了变量值 ""或者啥也不因引用,会调出来原有字符串的含义 ```bash #当没有引起来的时候,$是被认识的 [root@localhost ~]# echo $str1$str2 hellohello [root@localhost ~]# echo $str1 $str2 hello hello [root@localhost ~]# echo $str1 $str2 hello hello #用引号是代表一个整体 [root@localhost ~]# echo "$str1 $str2" hello hello #用!号在里面没有任何含义 [root@localhost ~]# echo "$str1 $str2!" hello hello! #但是在外面!是有含义的,代表调取上一个ls的命令。 [root@localhost ~]# !ls ``` 获取字符串的长度 ```bash [root@localhost ~]# str1=hello [root@localhost ~]# echo ${#str1} 5 [root@localhost ~]# ``` 截取字符信息(:0是从左到右进行,:0-1则为从右到左进行) ```bash [root@localhost ~]# str1=hello #截取hllo,(:0代表的从左到右第一个字符, :1代表的是从左到右输出1个字符) [root@localhost ~]# echo ${str1:0:1} h #截取hllo,(:0代表的从左到右第一个字符, :1代表的是从左到右输出2个字符) [root@localhost ~]# echo ${str1:0:2} he #截取hllo,(:1代表的从左到右第2个字符, :1代表的是从左到右输出1个字符) [root@localhost ~]# echo ${str1:1:1} e #截取hllo,(:1代表的从左到右第2个字符, :1代表的是从左到右输出2个字符) [root@localhost ~]# echo ${str1:1:2} el #截取hllo,(:1代表的从右到左第1个字符, :1代表的是从左到右输出2个字符) [root@localhost ~]# echo ${str1:0-1:2} o #同理 [root@localhost ~]# echo ${str1:0-1:1} o [root@localhost ~]# echo ${str1:0-2:1} l [root@localhost ~]# echo ${str1:0-2:2} lo ``` 对字符串内容进行正输出和反输出 ```bash #横向反输出hello [root@localhost ~]# str1=hello [root@localhost ~]# echo ${str1:0-1:1}${str1:0-2:1}${str1:0-3:1}${str1:0-4:1}${str1:0-5:1} olleh [root@localhost ~]# #竖向反输出(ken代表的是字符串的长度) [root@localhost ~]# str1=hello [root@localhost ~]# len=${#str1} [root@localhost ~]# echo $len 5 [root@localhost ~]# for i in `seq 1 $len`;do echo ${str1:0-$i:1};done o l l e h [root@localhost ~]# #利用for语句进行反输出(vim编辑中的最后一个echo是为了让输出内容进行换行) [root@localhost ~]# vim m123.sh #!/bin/bash str1=$1 len=${#str1} for i in `seq 1 $len` do echo -n ${str1:0-$i:1} done echo [root@localhost ~]# chmod +x m123.sh [root@localhost ~]# ./m123.sh 123 321 ``` 从指定字符串开始截取(意思就是用#号截右边的所有内容,要么就是用%号截取左边的所有内容) # 对内容从左向右第1个l开始截,保留右边的内容。(用#号) ```bash [root@localhost ~]# str1=hello [root@localhost ~]# echo ${str1#*l} lo ``` # 对内容从左向右第2个l开始截,保留右边的内容。(用#号) ```bash [root@localhost ~]# echo ${str1##*l} o ``` # 对内容从右向左第1个l开始截,保留左边的内容。(用%号) ```bash [root@localhost ~]# echo ${str1%l*} hel ``` # 对内容从右向左第2个l开始截,保留左边的内容。(用%号) ```bash [root@localhost ~]# echo ${str1%%l*} he ``` #也可以多个字符截取 ```bash [root@localhost ~]# echo ${str1#*el} lo ``` #实战效果 ```bash [root@localhost ~]# str1=/dev/sdb [root@localhost ~]# echo ${str1##*/} sdb [root@localhost ~]# str1=/etc/yum.repos.d [root@localhost ~]# echo ${str1##*/} yum.repos.d [root@localhost ~]# ``` ### printf格式化输出(利用该命令进行设定内容) *** ** * ** *** 常用格式替换符 | 替换符 | 功能 | |-----------|-----------------------------------------| | **%s** | **字符串** | | **%f** | **浮点格式,保留小数点位数%.nf,n为数字** | | %b | 相对应的参数中包括转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义 | | %c | ASCII字符,即显示对应参数的第一个字符 | | **%d,%i** | **十进制整数** | | %o | 八进制值 | | %u | 不带正负号的十进制值 | | %x | 十六进制值(a-f) | | %X | 十六进制值(A-F) | | **%%** | **表示%本身** | 常用转义字符 | **\\n** | **换行** | |---------|-----------| | **\\r** | **回车** | | **\\t** | **水平制表符** | #可以进行换行 ```bash [root@localhost ~]# printf "a\n" a ``` #百分数就是(%d%)但为10进制数,s%就是字符串(自行设定内容) ```bash [root@localhost ~]# printf "我名字是%s.今年%d岁! \n " lynn 23 我名字是lynn.今年23岁! [root@localhost ~]# printf "我名字是%s.今年%d岁! \n " lynn 23.5 -bash: printf: 23.5: 无效的数字 我名字是lynn.今年23岁! [root@localhost ~]# ``` #保留几位小数就是(%.几f),s%就是字符串(自行设定内容) ```bash [root@localhost ~]# printf "我名字是%s.今年%.2f岁! \n " lynn 23.5 我名字是lynn.今年23.50岁! [root@localhost ~]# printf "我名字是%s.今年%.2f岁! \n " lynn 23.54 我名字是lynn.今年23.54岁! [root@localhost ~]# printf "我名字是%s.今年%.3f岁! \n " lynn 23.546 我名字是lynn.今年23.546岁! [root@localhost ~]# ``` #对中间内容进行空格,(正的就是内容左边空几个,负的就是内容右边空几个) ```bash [root@localhost ~]# printf "我名字是%10s.今年%d岁! \n " lynn 23 我名字是 lynn.今年23岁! [root@localhost ~]# printf "我名字是%-10s.今年%d岁! \n " lynn 23 我名字是lynn .今年23岁! [root@localhost ~]# ``` ## 变量测试 *** ** * ** *** ###### 1.变量测试 ###### -v(看变量是否被定义) #检测出str5没有被定义内容 ```bash [root@localhost ~]# [ -v str5 ] [root@localhost ~]# echo $? 1 ``` ###### -R(看变量是否被引用 #可以看到是str5设置的变量,没有被用过 ```bash [root@localhost ~]# str5=666 [root@localhost ~]# [ -R str5 ] [root@localhost ~]# echo $? 127 ``` ###### 2.文件测试表达式 拓展:以前的知识:bash 后面跟文件名,可以直接执行该文件。 下面表格的选项可以按照案例格式,对文件进行检测 | 常用的文件测试操作符 | 说明 | |--------------|------------------------| | **-a/-e 文件** | **文件是否存在** | | **-d 文件** | 文件存在且为目录则为真,即测试表达式成立 | | **-f 文件** | 文件存在且为普通文件则为真,即测试表达式成立 | | **-r 文件** | **文件存在且可读为真** | | **-w 文件** | **文件存在且可写为真** | | **-x 文件** | **文件存在且可执行则为真** | #检测/boot,/boot1,/boot2是否存在 (利用-e) ```bash [root@localhost ~]# [ -e /boot ] [root@localhost ~]# echo $? 0 [root@localhost ~]# [ -e /boot1 ] [root@localhost ~]# echo $? 1 [root@localhost ~]# [ -e /boot2 ] [root@localhost ~]# echo $? 1 [root@localhost ~]# ``` ###### 3.字符串测试 拓展:unset str1(删除字符串1及其信息) | 常用字符串测试操作符 | 说明 | |-------------------|-----------------------------------------------------| | -n "字符串" | 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为nozero | | **-z "字符串"** | 若字符串的长度为0,则为真,z可以理解为zero | | \> | Ascii码是否大于Ascii码 | | "字符串1" == "字符串2" | 若字符串1内容等于字符串2内容,则为真 | | "字符串1" != "字符串2" | 若字符串1内容不等于字符串2内容,则为真 | | "字符串1" =\~ "字符串2" | 左侧字符串是否能被右侧的PATTERN所匹配。注意:此表达式用于\[\[ \]\]中:扩展的正则表达式 | #测试字符串123,根据上面的选项,输出为0为符合相应选项要求,非0则不符合。 ```bash [root@localhost ~]# str1=123 [root@localhost ~]# [ -n $str1 ] [root@localhost ~]# echo $? 0 [root@localhost ~]# [ -z $str1 ] [root@localhost ~]# echo $? 1 [root@localhost ~]# ``` #利用=\~时的操作,注意和上面的案例还是有区别的,中括号多了 ```bash [root@localhost ~]# str1=123 [root@localhost ~]# [[ $str1 =~ $str2 ]] [root@localhost ~]# echo $? 1 [root@localhost ~]# str2=123 [root@localhost ~]# [[ $str1 =~ $str2 ]] [root@localhost ~]# echo $? 0 ``` 拓展: #利用脚本进行设置信息(通过脚本进行简单的设置) ```bash [root@localhost ~]# vim test.sh #!/bin/bash [ -z $1 ] && echo "请输入参数!" || echo ok [root@localhost ~]# chmod +x test.sh [root@localhost ~]# ./test.sh 请输入参数! [root@localhost ~]# ./test.sh 1 ok ``` ###### 4.整数测试 | 在\[ \] 或 test中使用的比较符号 | 在(()) 或 \[\[ \]\]中使用的比较符号(不用这个做数字比较) | 说明 | |-----------------------|--------------------------------------|--------------------| | -eq | \\== 或 = | 相等,equal | | -ne | != | 不相等,not equal | | -gt | \> | 大于,greater than | | -ge | \> = | 大于等于,greater equal | | -lt | \< | 小于,less than | | -le | \< = | 小于等于,less equal | #利用上面的选项,进行利用功能,下面举例来证明上面选项怎么用 ```bash [root@localhost ~]# [ 1 -eq 1 ] [root@localhost ~]# echo $? 0 [root@localhost ~]# [ 1 -ne 1 ] [root@localhost ~]# echo $? 1 [root@localhost ~]# ``` ## 逻辑操作符 *** ** * ** *** | 在\[ \] 中使用的操作符 | 在test, \[\[ \]\] , (( ))中使用的逻辑操作符 | 说明 | |----------------|-----------------------------------|-------------------| | -a | \&\& | and,与,两边都为真,则结果为真 | | -o | \|\| | or,或,有真则真,同假则假 | | ! | ! | not,非,两端相反,则结果相反 | #举例说明,根据上面选项,符合要求输出为0,不符合就为非0。举例看格式 ```bash [root@localhost ~]# [ 1 -eq 3 -o 1 -gt 0 ] [root@localhost ~]# echo $? 0 [root@localhost ~]# [ 1 -eq 3 -a 1 -gt 0 ] [root@localhost ~]# echo $? 1 [root@localhost ~]# ``` 拓展: #利用\&\&和\|\| 操作,需添加中括号 ```bash [root@localhost ~]# [[ 1 -eq 3 && 1 -gt 0 ]] [root@localhost ~]# echo $? 1 [root@localhost ~]# [[ 1 -eq 3 || 1 -gt 0 ]] [root@localhost ~]# echo $? 0 [root@localhost ~]# ``` ## 关于()与 { } *** ** * ** *** 案例 #括号内的内容为子内容,子内容不用影响夫内容。 ```bash [root@localhost ~]# name=lisi;(echo $name;name=zhangsan;echo $name);echo $name lisi zhangsan lisi [root@localhost ~]# ``` #{}这个括号,会影响到父内容。 ```bash [root@localhost ~]# name=lisi; { echo $name;name=zhangsan;echo $name; } ;echo $name lisi zhangsan zhangsan [root@localhost ~]# ``` ### 实验演示1 *** ** * ** *** #磁盘空间的判断(看磁盘是不是充足),若超出80则提示空间不足。(没使用脚本) ```bash [root@localhost ~]# df -Th 文件系统 类型 大小 已用 可用 已用% 挂载点 /dev/mapper/openeuler-root ext4 69G 2.8G 63G 5% / devtmpfs devtmpfs 4.0M 0 4.0M 0% /dev tmpfs tmpfs 1.7G 0 1.7G 0% /dev/shm tmpfs tmpfs 4.0M 0 4.0M 0% /sys/fs/cgroup efivarfs efivarfs 256K 51K 201K 21% /sys/firmware/efi/efivars tmpfs tmpfs 675M 13M 662M 2% /run tmpfs tmpfs 1.7G 0 1.7G 0% /tmp /dev/sda2 ext4 974M 197M 710M 22% /boot /dev/sda1 vfat 599M 6.1M 593M 2% /boot/efi /dev/mapper/openeuler-home ext4 124G 40K 118G 1% /home [root@localhost ~]# jkl_=$(df |head -2 |tail -1 |tr -s ' '|cut -d ' ' -f5 |cut -d"%" -f1) [root@localhost ~]# echo $jkl_ 5 [root@localhost ~]# [ $jkl_ -gt 80 ] && echo "磁盘空间不足" || echo " 磁盘空间充足" 磁盘空间充足 ``` ## 实验演示2 *** ** * ** *** #### 使用read命令命令来接受输入 | Option | 说明 | |----------------|-----------------------------------------| | **-a array** | 把读取的数据赋值给数组array,从下标0开始 | | **-p prompt** | 显示提示信息,提示内容为prompt | | **-s** | 静默模式(Silent mode),不会再屏幕上显示输入的字符。例如:输入密码 | | **-t seconds** | 设置超时时间,单位为秒。如果用户没能按时完成,返回一个非0的退出状态 | #使用read,赋值多个数据进行输出,-p的选项为显示提示信息。注意看变量信息。-s为静默密码(在输入密码过程时没有显示)。 ```bash [root@localhost ~]# vim test.sh #!/bin/bash read -p "请输入你的姓名: " name echo "Hello,$name !" read -p "请输入你的年龄: " age [ $age -lt 30 ] && echo "Oh,行!" || echo "Oh,deadman,deadman!" read -s -p "请输入密码" password echo $password [root@localhost ~]# chmod +x test.sh [root@localhost ~]# ./test.sh 请输入你的姓名: lynn Hello,lynn ! 请输入你的年龄: 26 Oh,行! 请输入密码123 [root@localhost ~]# ./test.sh 请输入你的姓名: lynn Hello,lynn ! 请输入你的年龄: 66 Oh,deadman,deadman! 请输入密码123 [root@localhost ~]# ``` ## 流程控制 *** ** * ** *** 就是控制下当前的语句是否进行执行,或者是控制当前的语句在特定的条件下再执行。(可以让多个不同的指令相互关联起来了。) ##### if条件语句 1.单分支if ```bash [root@localhost ~]# vim if.sh #!/bin/bash #单分支 read -p "请输入一个数字: " number1 if [ $number1 -gt 10 ] then echo "$number1 大于 10" fi [root@localhost ~]# chmod +x if.sh [root@localhost ~]# ./if.sh 请输入一个数字: 66 66 大于 10 [root@localhost ~]# ``` 2.双分支if ```bash [root@localhost ~]# vim if.sh #!/bin/bash #双分支 read -p "请输入一个数字: " number1 if [ $number1 -gt 10 ] then echo "$number1 大于 10" else echo "$number1 不大于 10" fi [root@localhost ~]# ./if.sh 请输入一个数字: 8 8 不大于 10 [root@localhost ~]# ``` 3.多分支if(多条件) ```bash [root@localhost ~]# vim if.sh #!/bin/bash #双分支 read -p "请你的成绩: " socore if [ $socore -ge 90 -a $socore -le 100 ] then echo "A" elif [ $socore -ge 80 -a $socore -le 89 ] then echo "B" elif [ $socore -ge 60 -a $socore -le 79 ] then echo "C" elif [ $socore -ge 0 -a $socore -le 59 ] then echo "D" else echo "请输入0~100的成绩" fi [root@localhost ~]# ./if.sh 请你的成绩: 59 D [root@localhost ~]# ./if.sh 请你的成绩: 101 请输入0~100的成绩 [root@localhost ~]# ./if.sh 请你的成绩: 66 C [root@localhost ~]# ./if.sh 请你的成绩: 100 A [root@localhost ~]# ``` 4.父子if语句 #父子if条件命令的演示(利用脚本,以sshd服务为例,如果关闭就尝试将其打开) ```bash [root@localhost ~]# vim sshd_check_staus.sh #!/bin/bash process_stat=$(netstat -anpt | grep sshd | wc -l) if [ $process_stat -gt 0 ];then echo "sshd is running" systemctl status sshd | grep "active" else echo "sshd is running" systemctl start sshd sleep 2 process_stat=$( netstat -anpt | grep sshd wc -l ) if [ $process_stat -gt 0 ];then echo "sshd run successful" else echo "sshd no method to start,please check" fi fi [root@localhost ~]# chmod +x sshd_check_staus.sh [root@localhost ~]# ./sshd_check_staus.sh sshd is running Active: active (running) since Tue 2025-09-02 16:52:26 CST; 8h ago └─163391 grep active ```