脚本开发之ShellLinux小推车

[LN_11] Shell编程-条件判断&流程控制语句(

2019-02-10  本文已影响113人  Fighting_001

目录结构

一、条件判断式语句
    1. 按文件类型判断
    2. 按文件权限判断
    3. 两文件比较
    4. 两整数比较
    5. 字符串的判断
    6. 多重条件判断
二、单分支if语句
    1. 单分支if语句-语法结构
    2. 案例:判断当前登录用户是否是root
    3. 案例:判断分区使用率
三、双分支if语句
    1. 案例:判断输入的是否是一个目录
    2. 案例:判断Apache服务是否启动
四、多分支if语句
    1. 多分支if语句-语法结构
    2. 案例:计算器
    3. 案例:判断用户输入的是什么文件
五、多分支case条件语句
六、for循环
    1. for循环
        1)批量压缩&解压-案例
        2)从1开始的连续整数加和-案例
    2. 案例:批量添加&删除指定数量的用户
七、while循环 & until循环
    1. while循环
    2. until循环

一、条件判断式语句

1. 按文件类型判断

测试选项 作用
-b 文件 判断该文件:是否存在,且是否为块设备文件
(符合条件则为True)
-c 文件 判断该文件:是否存在,且是否为字符设备文件
-d 文件 判断该文件:是否存在,且是否为目录文件
-e 文件 判断该文件:是否存在
-f 文件 判断该文件:是否存在,且是否为普通文件
-L 文件 判断该文件:是否存在,且是否为符号链接文件
-p 文件 判断该文件:是否存在,且是否为管道文件
-s 文件 判断该文件:是否存在,且是否为非空
-S 文件 判断该文件:是否存在,且是否为套接字文件

两种判断格式
格式1:test -e /root/install.log
格式2:[ -e /root/install.log ]
PS:格式2中中括号两内侧需要连接空格

# 第1个判断目录若正确执行,则打印"yes",否则打印"no"
[ -d /root ] && echo "yes" || echo "no"
[ -e /root ] && echo "yes" || echo "no"
[ -f /root ] && echo "yes" || echo "no"
test -e /root/install.log && echo "yes" || echo "no"

2. 按文件权限判断

测试选项 作用
-r 文件 判断该文件:是否存在,且是否拥有权限
-w 文件 判断该文件:是否存在,且是否拥有权限
-x 文件 判断该文件:是否存在,且是否拥有执行权限
-u 文件 判断该文件:是否存在,且是否拥有SUID权限
-g 文件 判断该文件:是否存在,且是否拥有SGID权限
-k 文件 判断该文件:是否存在,且是否拥有SBit权限

通过执行者判断权限(rwx):所有者-所属组-其他人
如:ll 文件名

3. 两文件比较

测试选项 作用
文件1 -nt 文件2 判断修改时间新旧:是否文件1最新
文件1 -ot 文件2 判断修改时间新旧:是否文件1最旧
文件1 -ef 文件2 判断两个文件的Inode编号是否一致(是否同一个文件)
可用于判断硬链接

nt:new time
ot:old time
ef:equal file 是否为相同的源文件

# 创建硬链接
ln /tmp/test007/sh_Test/student.txt /tmp/test007/stu.txt
# 切换目录
cd /tmp/test007
# 判断是否同源的链接
[ ./stu.txt -ef ./sh_Test/student.txt ] && echo "yes" || echo "no"
# 查看Inode编号
ll -i ./stu.txt ./sh_Test/student.txt

4. 两整数比较

前提:A、B均为正数

测试选项 作用
A -eq B 判断:是否 [A == B]
A -ne B 判断:是否 [A != B]
A -gt B 判断:是否 [A > B]
A -lt B 判断:是否 [A < B]
A -ge B 判断:是否 [A >= B]
A -le B 判断:是否 [A <= B]

eq: equal
ne: not equal
gt: greater than
lt: less than
ge: greater or equal
le: less or equal

PS:Shell默认是字符串类型,但若加入了数值比较符号,则Shell会自动将比较的数值转为整型进行比较

[ 25 -ge 22 ] && echo "yes" || echo "no"
[ 25 -gt 22 ] && echo "yes" || echo "no"
[ 25 -ne 22 ] && echo "yes" || echo "no"
[ 25 -lt 22 ] && echo "yes" || echo "no"

5. 字符串的判断

测试选项 作用
-z 字符串 判断字符串:是否为空
-n 字符串 判断字符串:是否非空
字串1 == 字串2 判断字符串:是否 [字串1 == 字串2]
字串1 != 字串2 判断字符串:是否 [字串1 != 字串2]

PS:[ ! -z 字符串 ] 等效于 [ -n 字符串 ]

# 为name变量赋值
name=Jack
# 判断name变量是否为空
[ -z "$name" ] && echo "yes" || echo "no"
# 赋值
x=11 y=22
# 从字符串角度判断:x和y是否相等
[ "$x" == "$y" ] && echo "yes" || echo "no"
# 从整数角度判断:x和y是否相等
[ "$x" -eq "$y" ] && echo "yes" || echo "no"

6. 多重条件判断

测试选项 作用
判断1 -a 判断2 逻辑与。所有判断都成立,执行结果才为真
判断1 -o 判断2 逻辑或。只要有任意一个判断成立,执行结果即为真
! 判断 逻辑非。对原始的判断式取反

a:and
o:or

# 判断变量x是否非空,同时判断x是否大于20
x=22
[ -n "$x" -a "$x" -gt 20 ] && echo "yes" || echo "no"

二、单分支if语句

1. 单分支if语句-语法结构

语法结构:

# 风格1:
if [ 条件判断式 ];then
    程序主体
fi

# 风格2:
if [ 条件判断式 ]
    then
    程序主体
fi

语法说明:
① if语句使用fi结尾,和一般语言使用大括号结尾不同
[ 条件判断式 ]即test命令判断,中括号和条件判断式之间必须留有空格
then后跟满足条件的执行程序,then可放在[]之后,用";"分割;也可换一行写入then,此时不需要";"

2. 案例:判断当前登录用户是否是root

思路分析:
Step1-利用命令env可查看到 "USER=root"的一行内容,该字段的值即为当前登录的用户名称
Step2-提取USER字段值的过程:先用grep提取该行数据,然后用cut提取该行的第2列数据(分隔符为"="),即为USER字段的值
Step3-将提取的值赋给变量,再对变量与预期的名称root作比较,判断是否相等

测试命令:

# 查看环境变量
env
# 提取当前登录的用户
env | grep "USER" | cut -d "=" -f 2

程序实现:
vim if-001.sh

#!/bin/bash

test=$(env | grep "USER" | cut -d "=" -f 2)

if [ "$test" == "root" ]
    then
        echo "当前用户是root"
fi

配置权限:chmod 755 if-001.sh
执行脚本:./if-001.sh

3. 案例:判断分区使用率

提取挂载在根分区/的文件系统所用的容量占比是否超过90%

分析:

# 提取挂载点在根目录的一行数据(以下利用正则匹配)
df -h | grep "/$"
# 提取该行数据中 Use%字段值,对应位置变量:$4(具体根据不同系统而相异)
df -h | grep "/$" | awk '{print $4}'
# 对以上提取值处理:以 "%"为分隔符提取第 1列数据
df -h | grep "/$" | awk '{print $4}' | cut -d "%" -f 1

测试命令:

程序实现:
vim if-002.sh

#!/bin/bash

test=$(df -h | grep "/$" | awk '{print $4}' | cut -d "%" -f 1)

if [ "$test" -ge 90 ]
    then
        echo "根分区已接近满容量!"
fi

三、双分支if语句

1. 案例:判断输入的是否是一个目录

语法结构:

if [ 条件判断式 ]
    then
        执行程序1
    else
        执行程序2
fi

程序实现:
vim if-003.sh

#!/bin/bash

# 将用户输入的信息保存到变量dir中
read -t 30 -p "Please input a dir: " dir

if [ -d "$dir" ]
    then
        echo "Yes, it's a directory"
    else
        echo "No, it isn't a directory"
fi

2. 案例:判断Apache服务是否启动

# 查看系统中正在运行的进程
ps aux
# 过滤grep进程的干扰,筛选出只包含Apache的进程(httpd)
ps aux | grep "httpd" | grep -v "grep"

程序实现:
vim if-004.sh

#!/bin/bash

# 截取httpd进程,并把结果赋给变量test
test=$(ps aux | grep "httpd" | grep -v "grep")

# 若 test值非空,则执行 then中的命令,否则执行 else中的命令
if [ -n "$test" ]
    then
        # 输出正确重定向:将命令执行结果保存到指定文件中(追加到原内容之后)
        echo "$(date): httpd is OK" >> /tmp/test007/sh_Test/log/auto-acc.log
    else
        # 同时保存正确和错误输出的结果到指定文件中(覆盖)
        # 启动 httpd的命令根据各自配置而定(本次已配置过 phpstudy for Linux环境)
        phpstudy start &>/dev/null
        # 将命令执行结果保存到指定文件中(追加)
        echo "$(date): restart httpd !" >> /tmp/test007/sh_Test/log/auto-err.log
fi

chmod 755 if-004.sh
./if-004.sh

以上程序实现的效果,相当于是检测服务器上的Apache进程是否开启,若未开启则启动它,保持服务器运行httpd服务。可将该脚本部署在自动控制执行脚本的服务上,在指定的时间执行指定的脚本。

四、多分支if语句

1. 多分支if语句-语法结构

语法结构

if [ 条件判断式1 ]
    then
        执行程序1
elif [ 条件判断式2 ]
    then
        执行程序2
# 省略更多条件......
else
    执行程序n
fi

2. 案例:计算器

案例目标:
输入2个正整数,输入运算符,进行加减乘除运算

初步分析:
判断是否有输入(非空、空)
判断输入内容是否是数值(数字、非数字、空)
判断输入的运算符(匹配时进行相应格式计算)
执行运算符操作(加减乘除),输出结果

异常处理:
加减乘除:包含空、非数字时,不能参与运算,执行结果抛出异常提示
除法:除数不能为0,为0时抛出异常提示

程序实现:
vim if-005.sh

#!/bin/bash

# 将输入值or符号赋给对应的变量
read -p "请输入第1个数字:" num1
read -p "请输入第2个数字:" num2
read -p "请输入运算符[+-*/]:" opt

# 判断以上3个输入,若有任何一个输入为空,则执行then并退出程序
# 相当于是非空验证,只有3个输入都非空时才会执行后续语句
if [ -z "$num1" -o -z "$num2" -o -z "$opt" ]
  then
    echo "输入异常:不能包含空字符!"
    exit 1
# 判断3个输入都非空时,执行后续语句
elif [ -n "$num1" -a -n "$num2" -a -n "$opt" ]
  then
    # 将输入的字符中包含数字的部分全部替换为空
    # 若替换后的结果为空,则说明输入的是数字,否则为非数字
    test_1=$(echo $num1 | sed 's/[0-9]//g')
    test_2=$(echo $num2 | sed 's/[0-9]//g')

    # 对以上替换后的结果进行判断:2个结果中只要有任意一个是非空,则执行then并退出程序
    # 2个结果都为空时(输入的为数字),才会执行后续的elif
    if [ -n "$test_1" -o -n "$test_2" ]
      then
        echo "输入异常:请检查数字、运算符[+-*/]是否正确!"
        exit 2
    elif [ "$opt" == "+" ]  # 加法计算公式
      then
        result=$(($num1+$num2))
    elif [ "$opt" == "-" ]  # 减法计算公式
      then
        result=$(($num1-$num2))
    elif [ "$opt" == "*" ]  # 乘法计算公式
      then
        result=$(($num1*$num2))
    elif [ "$opt" == "/" ]  # 除法计算公式
      then
        # 除法时,对除数是否为零验证
        if [ "$num2" -eq 0 ]
          then
            echo "输入异常:除数不能为零!"
            exit 3
          else
            result=$(($num1/$num2))
        fi
    else  # 对以上4种运算符号[+-*/]以外的其他符号输入,抛出提示并退出程序
      echo "输入异常:请检查所输入数字、运算符[+-*/]的格式是否正确!"
      exit 4
    fi
fi

# 引用变量,输出[+-*/]通用的计算表达式
echo "$num1 $opt $num2 = $result"

PS:
①判断字符串是否相等(如:"$opt" == "+"),其中==左右两侧需要留有空格,否则执行可能报错
②以上程序中若使用test_opt=$(echo $opt | sed 's/[+*/-]//g')进行替换输入的加减乘除符号,对于乘号(*)暂时没有找到座位普通字符来处理的方案,默认会作为通配符;减号(-)在中括号[]中最好放在末尾位置,若放在其他位置可能引起系统执行歧义,如:范围的连接符

测试用例:

用例编号 输入数字1 输入数字2 输入运算符 预期输出
001 9 7 + 16
002 9 7 - 2
003 9 7 * 63
004 9 7 / 1
005 9 (空) + "输入异常:不能包含空字符!"
006 9 7 (空) "输入异常:不能包含空字符!"
007 9a 7 - 输入异常:请检查数字、运算符[+-*/]是否正确!
008 9 0 / 输入异常:除数不能为零!
009 9 7 abc 输入异常:请检查所输入数字、运算符[+-*/]的格式是否正确!
010 9 7 ** 输入异常:请检查所输入数字、运算符[+-*/]的格式是否正确!

授权&执行:
chmod 755 if-005.sh
./if-005.sh

执行结果:

以上,实际执行结果与预期输出相匹配

3. 案例:判断用户输入的是什么文件

初步分析:
判断输入是否非空
判断是否是系统存在的文件or目录
判断是否为普通文件
判断是否为目录文件

异常处理:
输入为空、不存在的文件or目录、普通文件or目录文件以外的文件

程序实现:
vim if-006.sh

#!/bin/bash

# 接收键盘输入,赋值给变量file
read -p "请输入一个目录名:" file

# 判断file变量是否为空,为空时抛出异常提示并退出程序
# file非空时,继续执行后续程序
if [ -z "$file" ]
  then
    echo "输入异常:不能为空!"
    exit 1
# 判断文件是否存在
elif [ ! -e "$file" ]
  then
    echo "输入异常:非系统存在的文件名称,请重新确认!"
    exit 2
# 判断是否为普通文件
elif [ -f "$file" ]
  then
    echo "$file:普通文件"
# 判断是否为目录文件
elif [ -d "$file" ]
  then
    echo "$file:目录文件"
else
  echo "$file:其他类型的文件"
fi

测试用例:

用例编号 输入 预期输出
001 /root /root:目录文件
002 hello.sh hello.sh:普通文件
003 /dev/sda1 /dev/sda1:其他类型的文件
004 (空) 输入异常:不能为空!
005 abcdeaaa 输入异常:非系统存在的文件名称,请重新确认!

执行结果:

五、多分支case条件语句

case语句与if...elif...else语句对比:
① 都是多分支条件语句
② case语句只能判断一种条件关系,而if语句可判断多种条件关系

语法结构:

case $变量名 in
  "值1")
    执行程序1
    ;;
  "值2")
    执行程序2
    ;;
  # ......
  *)
    执行程序n
    ;;
esac

PS:
1)case语句以case开头,esac结尾
2)变量匹配到某个值,则执行对应程序;每个执行程序后一行都需要加上双分号;;
3)最后一个取值用 *,不需要加双引号,此时表示以上所有取值都不满足时执行的程序

如:
vim case-001.sh

#!/bin/bash

echo "90-100,请输入 A"
echo "80-89,请输入 B"
echo "70-79,请输入 C"
echo "60-69,请输入 D"

read -p "请输入您的选择:" cho

case "$cho" in
  "A")
    echo "Score:90-100"
    ;;
  "B")
    echo "Score:80-89"
    ;;
  "C")
    echo "Score:70-79"
    ;;
  "D")
    echo "Score:60-69"
    ;;
  *)
    echo "Score is less than 60"
    ;;
esac

六、for循环

1. for循环

语法结构:

语法1
# 每次循环会将值依次赋给变量,变量如:i
for 变量 in 值1 值2 值3...
  do
    执行程序
  done
1)批量压缩&解压-案例

案例:先批量压缩文件为.tar.gz压缩包格式,然后再对其进行批量解压

Step1-批量压缩:
vim for-001.sh

#!/bin/bash

cd /tmp/test007/sh_Test

for i in $(ls if-*.sh)
  do
    # 压缩所有以"if-"开头的.sh文件为各自文件名命名的.tar.gz压缩文件
    tar -zcvf "$i"-.tar.gz $i
  done

# 将当期目录下的所有.tar.gz压缩包移动到./ysb目录下保存
mv *.tar.gz ./ysb

Step2-批量解压:
vim for-002.sh

#!/bin/bash

cd /tmp/test007/sh_Test/ysb

for i in $(ls *.tar.gz)
  do
    # 解压所有.tar.gz压缩包
    tar -zxvf $i
  done

# 将当期目录下的所有.tar.gz压缩包删除(保留解压文件)
rm -rf *.tar.gz

解压脚本也可写作如下方式:
vim for-003.sh

#!/bin/bash

cd /tmp/test007/sh_Test/ysb
ls *.tar.gz > ysb.log

for i in $(cat ysb.log)
  do
    # 解压所有.tar.gz压缩包,不显示详细的解压过程
    tar -zxf $i &>/dev/null
  done

# 将当期目录下的所有.tar.gz压缩包删除(保留解压文件)
# 同时删除保存压缩包文件名称的日志文件
rm -rf *.tar.gz ysb.log
语法2
for((初始值;循环控制条件;变量变化))
  do
    执行程序
  done

PS:
Shell脚本中,对于数值运算时,需要加双括号(())

2)从1开始的连续整数加和-案例

案例:1+2+3+...+100=?

vim for-004.sh

#!/bin/bash

s=0
for ((i=1;i<=100;i=i+1))
  do
    s=$(($s+$i))
  done

echo "1+2+3+...+100 = $s"

案例:1+2+3+...+N=?
对于正整数N,用户可自由输入不同的数值

vim for-005.sh

#!/bin/bash

echo "计算1+2+3+...+N=?"
read -p "请输入连续加和的最大整数 N:" num

s=0
for ((i=1;i<=$num;i=i+1))
  do
    s=$(($s+$i))
  done

echo "1+2+3+...+$num = $s"

2. 案例:批量添加&删除指定数量的用户

案例:批量添加指定数量的用户

vim for-006.sh

#!/bin/bash

# 根据提示进行输入
read -p "请输入用户名:" name
read -p "请输入添加的用户总数:" num
read -p "请输入用户的密码:" -s pass   # 输入隐藏显示

# 以上输入的3个数据都非空时,才会执行后续程序
if [ -n "$name" -a -n "$num" -a "$pass" ]
  then
    # 验证 $num是否是数字
    x=$(echo "$num" | sed 's/[0-9]//g')
    if [ -z "$x" ]
      then
        # 循环添加指定数量的用户
        for((i=1;i<=$num;i=i+1))
          do
            # 自增编号添加用户,添加过程不作输出显示
            /usr/sbin/useradd "$name-$i" &>/dev/null
            # 接收从标准输入的密码,赋予每个用户初始密码
            echo "$pass" | /usr/bin/passwd --stdin "$name-$i"
          done
    fi
fi

执行结果:

案例:批量删除指定的用户

先提取以上测试新增的用户名:
cat /etc/passwd | grep "/bin/bash" | grep -v -E "root|test001" | cut -d ":" -f 1

vim for-007.sh

#!/bin/bash

# 筛选出测试新值的所有用户,赋给变量 user
user=$(cat /etc/passwd | grep "/bin/bash" | grep -v -E "root|test001" | cut -d ":" -f 1)

for i in $user
  do
    # 循环删除筛选出的用户(连带删除其家目录)
    userdel -r $i
  done

执行结果:

七、while循环 & until循环

1. while循环

while循环是不定循环,也称条件循环。只要条件判断式成立,则循环就会一直继续,直到条件判断式不成立为止(应当避免永不停止的循环,即条件判断式不能固定为true不变)

PS:for循环是固定循环

语法结构:

while [ 条件判断式 ]
  do
    执行程序
  done

案例:1+2+3+...+N=?

vim while-001.sh

#!/bin/bash

read -p "请输入连续加和的最大整数N:" num

i=1 result=0
while [ $i -le $num ]
  do
    result=$(($result+$i))
    i=$(($i+1))
  done

echo "连加到数字$num的结果为:$result"

执行结果:

2. until循环

until循环和while循环相反,其只要条件判断式不成立则进行循环,一旦条件判断式成立时则终止循环

语法结构:

until [ 条件判断式 ]
  do
    执行程序
  done

vim until-001.sh

#!/bin/bash

read -p "请输入连续加和的最大整数N:" num

i=1 result=0
until [ $i -gt $num ]
  do
    result=$(($result+$i))
    i=$(($i+1))
  done

echo "连加到数字$num的结果为:$result"

执行结果:

PS:
以上while循环和until循环,若可能的话,建议用for循环来替代,以降低误操作产生永不停止循环的风险

上一篇 下一篇

猜你喜欢

热点阅读