Shell 编程之条件语句

发布于:2025-04-12 ⋅ 阅读:(33) ⋅ 点赞:(0)

目录

一、条件测试操作

1、文件测试

2、整数值比较

3、字符串比较

4、逻辑测试

二、if 条件语句

1、if 语句的结构

(1)单分支 if 语句

(2)双分支 if 语句

(3)多分支 if 语句

2、if 语句应用示例

(1)单分支 if 语句应用

(2)双分支 if 语句应用

(3)多分支 if 语句应用

三、case 分支语句

1、case 语句的结构

2、case 语句应用示例

(1)检查用户输入的字符类型

(2)编写系统服务脚本


一、条件测试操作

Shell 环境根据命令执行后的返回状态值($?)来判断是否执行成功,当返回值为 0 时表示成功否则(非 0 值)表示失败或异常。

使用专门的测试工具-test 命令,可以对特定条件进行测试,并根据返回值来判断条件是否成立(返回值为0表示条件成立)。
使用 test 测试命令时,包括以下两种形式:

test 条件表达式

[ 条件表达式 ]

这两种方式的作用完全相同,但相比前一种,后一种更为常用。注意方括号 “[” 或 “]” 与条件表达式之间需要至少一个空格进行分隔。

根据需要测试的条件类别不同,条件表达式也不同。常用的条件操作包括文件测试、整数值比较、字符串比较,以及针对多个条件的逻辑测试。

1、文件测试

文件测试指的是根据给定的路径名称,判断对应的是文件还是目录,或者判断文件是否可读、可写、可执行等。

文件测试的常见操作选项如下,使用时将测试对象放在操作选项之后即可。

-d(Directory)

测试是否为目录

-e(Exist)

测试目录或文件是否存在

-f(File)

测试是否为文件

-r(Read)

测试当前用户是否有权限读取

-w(write)

测试当前用户是否有权限写入

-x(Excute)

测试是否设置有可执行权限。

执行条件测试操作以后,通过预定义变量$?可以获得测试命令的返回状态值,从而判断该条件是否成立。

例如,执行以下操作可测试目录/media/是否存在,如果返回值$?为 0,表示存在此目录,否则表示不存在或者虽然存在但不是目录。

[root@localhost ~]# [ -d /media/ ]
[root@localhost ~]# echo $?     #查看前一命令的返回值
0    #返回 0 表示条件成立

若测试的条件不成立,则测试操作的返回值将不为0 (通常为 1)。

例如,执行以下操作展示了测试不存在目录的情况。

[root@localhost ~]# [ -d /media/cdrom/Server ]
[root@localhost ~]# echo $?
1    #返回1表示条件不成立

通过查看变量$?的值可以判断前一步的条件测试结果,但是操作比较烦琐,输出结果也并不是很直观。为了更直观地査看测试结果,可以结合命令分隔符“&&”和 echo 命令一起使用,当条件成立时直接输出“YES”。

“&&”符号表示“而且”的关系,只有当前面的命令执行成功后才会执行后面的命令,否则后面的命令将会被忽略。

例如,上述目录测试操作可以改写如下。

[root@localhost ~]# [ -d /media/cdrom/Server ] && echo "YES"
    #无输出表示该目录不存在
[root@localhost ~]# [ -d /media/ ] && echo "YES"
YES    #输出“YES”表示该目录存在

2、整数值比较

整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否大于、等于、小于第二个数。如下为常用的操作选项,使用时将操作选项放在要比较的两个整数之间。

-eq(Equal)

第一个数等于第二个数。

-ne(Not Equal)

第一个数不等于第二个数。

-gt(Greater Than)

第一个数大于第二个数。

-lt(Lesser Than)

第一个数小于第二个数。

-le(Lesser or Equal)

第一个数小于或等于第二个数。

-ge(Greater or Equal)

第一个数大于或等于第二个数。

整数值比较在 Shell 脚本编写中的应用较多。

例一:若要判断当前已登录的用户数,当超过两个时输出“Too many.”,可以执行以下操作。其中,已登录用户数可通过“who | wc -l”命令获得,以命令替换方式嵌入。

[root@localhost ~]# unum=`who | wc -l`
[root@localhost ~]# [ $unum -gt 2 ] && echo "Too many."
     #无输出表示结果小于用户登录数

例二:若要判断物理内存(Mem)当前的磁盘缓存(buff/cache)大小,当低于 1024MB 时输出具体数值,可以执行以下操作。其中,“free -m”命令表示以 MB 为单位输出内存信息,提取的空闲内存数值通过命令替换赋值给变量Freecc。

[root@localhost ~]# FreeCC=$(free -m | grep "Mem:" | awk '{print $6}')
[root@localhost ~]# [ $FreeCC -lt 1024 ] && echo ${FreeCC}MB
585MB

3、字符串比较

字符串比较通常用来检査用户输入、系统环境等是否满足条件,在提供交互式操作的 Shell 脚本中,也可用来判断用户输入的位置参数是否符合要求。字符串比较的常用操作选项如下。

=

第一个字符串与第二个字符串相同。

!=

第一个字符串与第二个字符串不相同,其中“!”符号表示取反。

-z

检查字符串是否为空(Zero),对于未定义或赋予空值的变量将视为空串。

例一:若要判断当前系统的语言环境,当发现不是“en.Us”时输出提示信息“Not en.Us”,可以
执行以下操作。

[root@localhost ~]# echo $LANG
zh_CN.UTF-8    #查看当前的语言环境
[root@localhost ~]# [ $LANG != "en.US" ] && echo "Not en.US"
Not en.US    #字符串测试结果(不等于)

例二:在 shell 脚本应用中,经常需要用户输入“yes”或“no”来确认某个任务。以下操作展示了确认交互的简单过程,当然,实际使用时还会根据变量“ACK”的取值分别执行进一步的操作。

[root@localhost ~]# read -p "是否覆盖现有(yes/no)?" ACK
是否覆盖现有(yes/no)?yes
[root@localhost ~]# [ $ACK = "yes" ] && echo "覆盖"
覆盖
[root@localhost ~]# read -p "是否覆盖现有文件(yes/no)?" ACK
是否覆盖现有文件(yes/no)?no    
[root@localhost ~]# [ $ACK = "no" ] && echo "不覆盖"
不覆盖

4、逻辑测试

逻辑测试指的是判断两个或多个条件之间的依赖关系。当系统任务取决于多个不同的条件时,根据这些条件是否同时成立或者只要有其中一个成立等情况,需要有一个测试的过程。

常用的逻辑测试操作如下,使用时放在不同的测试语句或命令之间。

&&

逻辑与,表示“而且”,只有当前后两个条件都成立时,整个测试命令的返回值才为 0(结果成立)。使用 test 命令测试时,“&&”可改为“-a”。

||

逻辑或,表示“或者”,只要前后两个条件中有一个成立,整个测试命令的返回值即为0 (结果成立)。使用 test 命令测试时,“||”可改为“-o”。

!

逻辑否,表示“不”,只有当指定的条件不成立时,整个测试命令的返回值才为 0(结果成立)。

在上述逻辑测试的操作选项中,“&&”和“||”通常也用于间隔不同的命令操作,其作用是相似的。

就如编译安装的“make && make install”。

例如,若要判断当前 Linux 系统的内核版本是否大于 6.4,可以执行以下操作。其中,内核版本号通过uname和awk 命令活得。

[root@localhost ~]# uname -r    #查看内核版本信息
6.6.0-72.0.0.76.oe2403sp1.x86_64
[root@localhost ~]# mnum=$(uname -r | awk -F. '{print $1}')    #取主版本号
[root@localhost ~]# snum=$(uname -r | awk -F. '{print $2}')    #取次版本号
[root@localhost ~]# [ $mnum -ge 6 ] && [ $snum -gt 4 ] && echo "符合要求"
符合要求

二、if 条件语句

1、if 语句的结构

(1)单分支 if 语句

if 语句的“分支”指的是不同测试结果所对应的执行语句(一条或多条)。对于单分支的选择结构,只有在“条件成立”时才会执行相应的代码,否则不执行任何操作。

单分支 if 语句的语法格式如下所示:

if 条件测试操作
then
    命令序列
fi

条件测试操作既可以是 “[条件表达式]” 语句,也可以是其他可执行的命令语句;

命令序列指的是一条或多条可执行的命令行,也包括嵌套使用的 if 语句或其他流程控制语句。

单分支 if 语句的执行流程:首先判断条件测试操作的结果,如果返回值为 0,表示条件成立,执行后面的命令序列,一直到遇见 fi 结束判断为止,继续执行其他脚本代码;如果返回值不为 0,则then忽略 then 后面的命令序列,直接跳至 fi 行以后执行其他脚本代码。

(2)双分支 if 语句

对于双分支的选择结构,要求针对“条件成立”“条件不成立”两种情况分别执行不同的操作。

双分支 if 语句的语法格式如下所示:

if 条件测试操作
then
    命令序列1
else
    命令序列2
fi

双分支 if 语句的执行流程:首先判断条件测试操作的结果,如果条件成立,则执行 then 后面的命令序列1,忽略 else 及后面的命令序列 2,直到遇见 fi 结束判断;如果条件不成立,则忽略 then 及后面的命令序列1,直接跳至 else 后面的命令序列 2 并执行,直到遇见 fi 结束判断。

(3)多分支 if 语句

由于 if 语句可以根据测试结果的成立、不成立分别执行操作,所以能够嵌套使用,进行多次判断。

多分支 if 语句的语法格式如下:

if 条件测试操作1
then
    命令序列1
elif 条件测试操作2
then
    命令序列2
else
    命令序列3
fi

上述语句结构中只嵌套了一个 elif 语句作为示例,实际上可以嵌套多个。if 语句的嵌套在编写Shell 脚本时并不常用,因为多重嵌套容易使程序结构变得复杂。当确实需要使用多分支的程序结构时通常采用下文中的 case 语句更加方便。

多分支 if 语句的执行流程:首先判断条件测试操作1的结果,如果条件1成立,则执行命令序列1,然后跳至 fi 结束判断;如果条件 1 不成立,则继续判断条件测试操作2的结果,如果条件2成立,则执行命令序列 2,然后跳至 fi 结束判断……如果所有的条件都不满足,则执行 else 后面的命令序列n,直到遇见 fi 结束判断。

2、if 语句应用示例

(1)单分支 if 语句应用

很多Linux用户习惯上将光盘设备挂载到/media/cdrom 目录下,但 Linux 系统默认并没有建立此目录。若需要在 Shell 脚本中执行挂载光盘的操作,建议先判断挂载点目录是否存在,若不存在则新建此目录。

[root@localhost ~]# vim chkmountdir.sh
#!/bin/bash
MOUNT_DIR="/media/cdrom"
if [ ! -d $MOUNT_DIR ]
then
    mkdir -p $MOUNT_DIR
fi
[root@localhost ~]# bash chkmountdir.sh 

例如,有些特权命令操作要求以 root 用户执行,如果当前用户不是 root,那么再执行这些命令就没有必要(肯定会失败)。针对这种情况,在脚本中可以先判断当前用户是不是root,如果不是则报错并执行“exit 1”命令退出脚本(1 表示退出后的返回状态值),而不再执行其他代码。

[root@localhost ~]# vim /opt/chkifroot.sh
#!/bin/bash
if [ "$USER" != "root" ]
then
    echo "错误!非root用户,权限不足!"
    exit 1
fi
fdisk -l /dev/nvme0n1

当普通用户zhangsan执行 chkifroot.sh 脚本时,由于“非 root 用户”的条件成立,因此会提示权限不足并退出脚本(使用“exit 1”退出脚本后,fi 之后的 fdisk 命令将不会执行)。

[zhangsan@localhost root]$ bash /opt/chkifroot.sh 
错误!非root用户,权限不足!
[zhangsan@localhost root]$ exit
exit
[root@localhost ~]# bash /opt/chkifroot.sh 
Disk /dev/nvme0n1:200 GiB,214748364800 字节,419430400 个扇区
......省略部分内容

(2)双分支 if 语句应用

双分支 if 语句只是在单分支的基础上针对“条件不成立”的情况执行另一种操作,而不是“坐视不管”地不执行任何操作。

例如,若要编写一个连通性测试脚本 pinghost.sh,通过位置参数$1 提供目标主机地址,然后根据 ping 检测结果给出相应的提示,可以参考以下操作过程。

[root@localhost ~]# vim pinghost.sh
#!/bin/bash
ping -c 3 -i 0.2 -W 3 $1 &>/dev/null
if [ $? -eq 0 ]
then    
    echo "Host $1 is up."
else
    echo "Host $1 is down."
fi

-c:发送的测试包数。

-i:间隔时间。

-W:超时时间。

&>/dev/null:屏蔽了ping命令执行过程的输出信息。

[root@localhost ~]# bash pinghost.sh 192.168.10.102
Host 192.168.10.102 is up.
[root@localhost ~]# bash pinghost.sh 192.168.1.103
Host 192.168.1.103 is down.

例如,通过 shell 脚本检査 vsftpd 服务是否运行,如果已经运行则列出其监听地址、PID 号,否则输出提示“警告:vsftpd 服务不可用!”。其中,pgrep 命令的“-x”选项表示查找时使用精确匹配。

[root@localhost ~]# vim chkvsftpd.sh
#!/bin/bash
systemctl status vsftpd &>/dev/null
if [ $? -eq 0 ]
then
    echo "监听地址:$(netstat -anpt | grep vsftpd | awk '{print $4}')"
    echo "进程PID号:$(pgrep -x vsftpd)"
else
    echo "警告:vsftpd服务不可用!"
fi 
[root@localhost ~]# bash chkvsftpd.sh 
警告:vsftpd服务不可用!
[root@localhost ~]# systemctl start vsftpd
[root@localhost ~]# bash chkvsftpd.sh 
监听地址::::21
进程PID号:9519

(3)多分支 if 语句应用

与单分支、双分支 if 语句相比,多分支if 语句的结构能够根据多个互斥的条件分别执行不同的探作,实际上等同于嵌套使用的 if 语句。

例如,若要编写一个成绩分档的脚本 gradediv.sh,根据输入的考试分数不同来区分优秀、合格、不合格三挡,可以参考以下操作过程。

[root@localhost ~]# vim gradediv.sh
#!/bin/bash
read -p "请输入您的分数(0-100):" GRADE
if [ $GRADE -ge 80 ] && [ $GRADE -le 100 ]
then
    echo "$GRADE 分,优秀!"
elif [ $GRADE -ge 60 ] && [ $GRADE -le 80 ]
then
    echo "$GRADE 分,合格!"
else
    echo "$GRADE 分,不合格!"
fi

效果如下:

[root@localhost ~]# bash gradediv.sh 
请输入您的分数(0-100):100
100 分,优秀!
[root@localhost ~]# bash gradediv.sh 
请输入您的分数(0-100):77
77 分,合格!
[root@localhost ~]# bash gradediv.sh 
请输入您的分数(0-100):50
50 分,不合格!

三、case 分支语句

1、case 语句的结构

case 语句主要适用于以下情况:某个变量存在多种取值,需要对其中的每一种取值分别执行不同的命令序列。这种情况与多分支的 if 语句非常相似,只不过 if 语句需要判断多个不同的条件,而 case语句只是判断一个变量的不同取值。

case 分支语句的语法结构如下:

case 变量值 in
模式1)
    命令序列1
;;
模式2)
    命令序列2
;;......
*)
    默认命令序列
esac

关键字 case 后面跟的是“变量值”,即“$变量名”。整个分支结构包括在case...esac 之间,中间的模式1、模式2、…、*对应为变量的不同取值(程序期望的取值),其中*作为通配符,可匹配任意值。

case 语句的执行流程:首先使用“变量值”与模式 1 进行比较,若取值相同则执行模式1后的命
令序列,直到遇见双分号“;;”后跳转至 esac,表示结束分支;若与式1不相匹配,则继续与模式2进行比较,若取值相同则执行模式 2后的命令序列,直到遇见双分号“;;”后跳转至 esac,表示结束分支.…依此类推,若找不到任何匹配的值,则执行默认模式“*)”后的命令序列,直到遇见 esac 后结束分支。

case 行尾必须为单词“in”,每一式必须以右括号“)”结束。
双分号“;;”表示命令序列的结束。
模式字符串中,可以用方括号表示一个连续的范围,如“[0-9]”;还可以用竖杠符号“|”表示或,如“A|B”。
最后的“*)”表示默认模式,其中的*相当于通配符,

2、case 语句应用示例

(1)检查用户输入的字符类型

提示用户从键盘输入一个字符,通过 case 语句判断该字符是否为字母、数字或者其他控制字符,并给出相应的提示信息。

[root@localhost ~]# vim hitkey.sh
#!/bin/bash
read -p "请输入一个字符,并按回车键确认:" KEY
case "$KEY" in
[a-z]|[A-Z])
    echo "您输入的是字母。"
;;
[0-9])
    echo "您输入的是数字。"
;;
*)
    echo "您输入的是其他字符。"
esac

 效果如下:

[root@localhost ~]# bash hitkey.sh 
请输入一个字符,并按回车键确认:K
您输入的是字母。
[root@localhost ~]# bash hitkey.sh 
请输入一个字符,并按回车键确认:8
您输入的是数字。
[root@localhost ~]# bash hitkey.sh 
请输入一个字符,并按回车键确认:>
您输入的是其他字符。

(2)编写系统服务脚本

编写一个名为 myprog 的系统服务脚本,通过位置变量$1 指定的 start、stop、restart、status控制参数,分别用来启动、停止、重启 sleep 进程,以及査看 sleep 进程的状态。其中,命令 sleep 用来暂停指定秒数的时间,这里仅用做测试,在实际运维工作中应将 sleep 改为相应后台服务的控制命令序列。

[root@localhost ~]# vim myprog
#!/bin/bash
case "$1" in
start)
      echo -n "正在启动 sleep 服务 ..."
    if sleep 7200 &
    then
      echo "ok"
    fi
    ;;
stop)
      echo -n "正在停止 sleep 服务..."
    pkill "sleep" &>/dev/null
      echo "OK"
    ;;
status)
    if pgrep "sleep" &>/dev/null
    then
      echo "sleep 服务已经启动."
    else
      echo "sleep 服务已经停止."
    fi
    ;;
restart)
    bash $0 stop
    bash $0 start
    ;;
*)
      echo "用法:$0 {start|stop|status|restart}"
esac

以下是效果:

[root@localhost ~]# bash myprog start
正在启动 sleep 服务 ...ok
[root@localhost ~]# bash myprog restart
正在停止 sleep 服务...OK
正在启动 sleep 服务 ...ok
[root@localhost ~]# bash myprog status
sleep 服务已经启动.
[root@localhost ~]# bash myprog stop
正在停止 sleep 服务...OK
[root@localhost ~]# bash myprog reload
用法:myprog {start|stop|status|restart}

若想要将myprog服务交给systemd来管理,可以在/lib/systemd/system目录下添加相应的myprog.service配置文件。


网站公告

今日签到

点亮在社区的每一天
去签到