Shell中的数值运算

    xiaoxiao2022-06-26  130

    Shell中的数值运算 问题

    本案例要求熟悉Linux Shell环境的特点,主要练习以下操作:

    使用expr、$[ ]、let等整数运算工具:定义变量X=1234,然后计算X与78的四则运算及求模结果使用bc实现小数运算操作:以交互方式计算12.34与56.78的四则运算结果,另外再以非交互方式重复上述计算,最多显示4位小数 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:整数运算工具

    1)使用expr命令

    乘法操作应采用 \* 转义,避免被作为Shell通配符;参与运算的整数值与运算操作符之间需要以空格分开,引用变量时必须加$符号。

    首先定义变量X=1234,然后分别计算与78的加减乘除和求模运算结果:

    [root@svr5 ~]# X=1234   //定义变量X

    [root@svr5 ~]# expr  $X  +  78   //加法

    1312

    [root@svr5 ~]# expr  $X  -  78    //减法

    1156

    [root@svr5 ~]# expr  $X  \*  78   //乘法,操作符应添加\转义

    96252

    [root@svr5 ~]# expr  $X  /  78   //除法,仅保留整除结果

    15

    [root@svr5 ~]# expr  $X  %  78 //求模

    64

    2)使用$[]或$(())表达式

    乘法操作*无需转义,运算符两侧可以无空格;引用变量可省略 $ 符号;计算结果替换表达式本身,可结合echo命令输出。

    同样对于变量X=1234,分别计算与78的加减乘除和求模运算结果:

    [root@svr5 ~]# X=1234   

    [root@svr5 ~]# echo $[X+78]

    1312

    [root@svr5 ~]# echo $[X-78]

    1156

    [root@svr5 ~]# echo $[X*78]

    96252

    [root@svr5 ~]# echo $[X/78]

    15

    [root@svr5 ~]# echo $[Xx]

    64

    3)使用let命令

    expr或$[]、$(())方式只进行运算,并不会改变变量的值;而let命令可以直接对变量值做运算再保存新的值。因此变量X=1234,在执行let运算后的值会变更;另外,let运算操作并不显示结果,但是可以结合echo命令来查看:

    [root@svr5 ~]# X=1234  

    [root@svr5 ~]# let X+=78 ; echo $X

    1312

    [root@svr5 ~]# let X-=78 ; echo $X

    1234

    [root@svr5 ~]# let X*=78 ; echo $X

    96252

    [root@svr5 ~]# let X/=78 ; echo $X

    1234

    [root@svr5 ~]# let X%=78 ; echo $X

    64

    步骤二:小数运算工具

    1)bc交互式运算

    先执行bc命令进入交互环境,然后再输入需要计算的表达式。以计算小数12.34与5.678的四则运算为例,相关操作如下:

    [root@svr5 ~]# bc

    bc 1.06.95

    Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.

    This is free software with ABSOLUTELY NO WARRANTY.

    For details type `warranty'.

    12.34+56.78 //加法

    69.12

    12.34-56.78 //减法

    -44.44

    12.34*56.78 //乘法

    700.66

    12.34/56.78 //除法

    0

    quit   //退出交互计算器

    [root@svr5 ~]#

    2)bc非交互式运算

    将需要运算的表达式通过管道操作交给bc运算。注意,小数位的长度可采用scale=N限制,除此以外也受参与运算的数值的小数位影响。以计算小数12.34与5.678的四则运算为例,相关操作如下:

    [root@svr5 ~]# echo 'scale=4;12.34+5.678' | bc

    18.018

    [root@svr5 ~]# echo 'scale=4;12.34-5.678' | bc

    6.662

    [root@svr5 ~]# echo 'scale=4;12.34*5.678' | bc

    70.0665

    [root@svr5 ~]# echo 'scale=4;12.34/5.678' | bc

    2.1733

    条件测试操作 问题

    本案例要求参考PPT上的示例,分别练习以下条件测试操作:

    识别文件/目录的状态比较整数值的大小字符串匹配多个条件/操作的逻辑组合 步骤

    实现此案例需要按照如下步骤进行。

    步骤一:条件测试的基本用法

    1)一般用法

    使用“test 表达式”或者[ 表达式 ]都可以,表达式两边至少要留一个空格。比如,判断变量X的值是否大于5,操作如下:

    [root@svr5 ~]# X=10

    [root@svr5 ~]# test $X -gt 5

    上述test测试也可改用 [ ]方式(推荐用这种方式):

    [root@svr5 ~]# [ $X -gt 5 ]

    [root@svr5 ~]#

    条件测试操作本身不显示出任何信息。测试的条件是否成立主要体现在命令执行后的返回状态(即 $?),所以可以在测试后查看变量$?的值来做出判断,或者结合&&、||等逻辑操作显示出结果(或作其他操作) 。

    比如,分别测试变量X的值(10)是否大于5、是否大于20:

    [root@svr5 ~]# [ $X -gt 5 ]

    [root@svr5 ~]# echo $?

    0   //返回值为0,说明测试的条件成立

    [root@svr5 ~]# [ $X -gt 20 ]

    [root@svr5 ~]# echo $?

    1   //返回值不为0,说明测试的条件不成立

    或者,结合逻辑分隔更直接的给出结果:

    [root@svr5 ~]# [ $X -gt 5 ] && echo "YES" || echo "NO"

    YES   //因为10>5,所以 YES

    [root@svr5 ~]# [ $X -gt 20 ] && echo "YES" || echo "NO"

    NO   //因为 10<20,所以 NO

    2)扩展用法

    test本身是一个命令,所以往宽了理解,任何命令都可以用作测试,因为在Shell环境中每个命令执行完后都会有返回状态$?。比如,若要检查一个软件包是否安装,正常可以执行“rpm -q 软件名”查询,人工可以直接看显示结果,脚本却没有那么智能,怎么办呢,就看返回值 $?。

    比如,分别查询一个已安装的包、未安装的包,$? 返回值是不同的:

    [root@svr5 ~]# rpm -q httpd

    httpd-2.2.3-74.el5

    [root@svr5 ~]# echo $?

    0

    [root@svr5 ~]# rpm -q nginx

    package nginx is not installed

    [root@svr5 ~]# echo $?

    1

    转化为测试操作,结合逻辑分隔提示结果:

    [root@svr5 ~]# rpm -q httpd &> /dev/null && echo "已安装" || echo "未安装"

    已安装

    [root@svr5 ~]# rpm -q nginx &> /dev/null && echo "已安装" || echo "未安装"

    未安装

    步骤二:识别文件/目录的状态

    1)-e 判断对象是否存在(不管是目录还是文件)

    [root@svr5 ~]# [ -e "/usr/src/" ] && echo "存在" || echo "不存在"

    存在

    [root@svr5 ~]# [ -e "/etc/fstab" ] && echo "存在" || echo "不存在"

    存在

    [root@svr5 ~]# [ -e "/home/nooby" ] && echo "存在" || echo "不存在"

    不存在

    2)-d 判断对象是否为目录(存在且是目录)

    [root@svr5 ~]# [ -d "/usr/src/" ] && echo "是目录" || echo "不是目录"

    是目录

    [root@svr5 ~]# [ -d "/etc/fstab" ] && echo "是目录" || echo "不是目录"

    不是目录

    [root@svr5 ~]# [ -d "/home/nooby" ] && echo "是目录" || echo "不是目录"

    不是目录

    3)-f 判断对象是否为文件(存在且是文件)

    [root@svr5 ~]# [ -f "/usr/src/" ] && echo "是文件" || echo "不是文件"

    不是文件

    [root@svr5 ~]# [ -f "/etc/fstab" ] && echo "是文件" || echo "不是文件"

    是文件

    [root@svr5 ~]# [ -f "/home/nooby" ] && echo "是文件" || echo "不是文件"

    不是文件

    4)-r 判断对象是否可读

    此测试对root用户无效,无论文件是否设置r权限,root都可读:

    [root@svr5 ~]# cp install.log /tmp/rtest.txt   //复制一个文件做测试

    [root@svr5 ~]# chmod -r /tmp/rtest.txt   //去掉所有的r权限

    [root@svr5 ~]# ls -ld /tmp/rtest.txt   //确认设置结果

    --w------- 1 root root 33139 12-11 10:43 /tmp/rtest.txt

    [root@svr5 ~]# [ -r "/tmp/rtest.txt" ] && echo "可读" || echo "不可读"

    可读   //root测试结果仍然可读

    切换为普通用户,再执行相同的测试,结果变为“不可读”:

    [zengye@svr5 ~]$ [ -r "/tmp/rtest.txt" ] && echo "可读" || echo "不可读"

    不可读

    普通用户只对自己拥有r权限的文件或目录,-r测试时结果才成立:

    [zengye@svr5 ~]$ ls -l .bashrc

    -rw-r--r-- 1 zengye zengye 124 09-24 16:44 .bashrc

    [zengye@svr5 ~]$ [ -r ".bashrc" ] && echo "可读" || echo "不可读"

    可读

    5)-w 判断对象是否可写

    此测试同样对root用户无效,无论文件是否设置w权限,root都可写:

    [root@svr5 ~]# chmod -w /tmp/rtest.txt  //去掉所有的w权限

    [root@svr5 ~]# ls -l /tmp/rtest.txt   //确认设置结果

    ---------- 1 root root 33139 12-11 10:43 /tmp/rtest.txt

    [root@svr5 ~]# [ -w "/tmp/rtest.txt" ] && echo "可写" || echo "不可写"

    可写

    切换为普通用户,可以正常使用-w测试:

    [zengye@svr5 ~]$ ls -l /tmp/rtest.txt

    ---------- 1 root root 33139 12-11 10:52 /tmp/rtest.txt

    [zengye@svr5 ~]$ [ -w "/tmp/rtest.txt" ] && echo "可写" || echo "不可写"

    不可写

    [zengye@svr5 ~]$ ls -l .bashrc

    -rw-r--r-- 1 zengye zengye 124 09-24 16:44 .bashrc

    [zengye@svr5 ~]$ [ -w ".bashrc" ] && echo "可写" || echo "不可写"

    可写

    6)-x 判断对象是否具有可执行权限

    这个取决于文件本身、文件系统级的控制,root或普通用户都适用:

    [root@svr5 ~]# chmod 644 /tmp/rtest.txt   //重设权限,无x

    [root@svr5 ~]# ls -l /tmp/rtest.txt   //确认设置结果

    -rw-r--r-- 1 root root 33139 12-11 10:52 /tmp/rtest.txt

    [root@svr5 ~]# [ -x "/tmp/rtest.txt" ] && echo "可执行" || echo "不可执行"

    不可执行

    [root@svr5 ~]# chmod +x /tmp/rtest.txt   //添加x权限

    [root@svr5 ~]# [ -x "/tmp/rtest.txt" ] && echo "可执行" || echo "不可执行"

    可执行

    步骤三:整数值比较

    参与比较的必须是整数(可以调用变量),比较非整数值时会出错:

    [root@svr5 ~]# A=20.4

    [root@svr5 ~]# [ $A -gt 10 ]   //不支持小数比较

    -bash: [: 20.4: integer expression expected

    1)-eq 比较两个数是否相等。

    [root@svr5 ~]# X=20   //定义一个测试变量

    [root@svr5 ~]# [ $X -eq 20 ] && echo "相等" || echo "不相等"

    相等

    [root@svr5 ~]# [ $X -eq 30 ] && echo "相等" || echo "不相等"

    不相等

    2)-ne 比较两个数是否不相等。

    [root@svr5 ~]# [ $X -ne 20 ] && echo "不等于" || echo "等于"

    等于

    [root@svr5 ~]# [ $X -ne 30 ] && echo "不等于" || echo "等于"

    不等于

    3)-gt 比较前面的整数是否大于后面的整数。

    [root@svr5 ~]# [ $X -gt 10 ] && echo "大于" || echo "否"

    大于

    [root@svr5 ~]# [ $X -gt 20 ] && echo "大于" || echo "否"

    [root@svr5 ~]# [ $X -gt 30 ] && echo "大于" || echo "否"

    4)-ge 比较前面的整数是否大于或等于后面的整数。

    [root@svr5 ~]# [ $X -ge 10 ] && echo "大于或等于" || echo "否"

    大于或等于

    [root@svr5 ~]# [ $X -ge 20 ] && echo "大于或等于" || echo "否"

    大于或等于

    [root@svr5 ~]# [ $X -ge 30 ] && echo "大于或等于" || echo "否"

    5)-lt 比较前面的整数是否小于后面的整数。

    [root@svr5 ~]# [ $X -lt 10 ] && echo "小于" || echo "否"

    [root@svr5 ~]# [ $X -lt 20 ] && echo "小于" || echo "否"

    [root@svr5 ~]# [ $X -lt 30 ] && echo "小于" || echo "否"

    小于

    6)-le 比较前面的整数是否小于或等于后面的整数。

    [root@svr5 ~]# [ $X -le 10 ] && echo "小于或等于" || echo "否"

    [root@svr5 ~]# [ $X -le 20 ] && echo "小于或等于" || echo "否"

    小于或等于

    [root@svr5 ~]# [ $X -le 30 ] && echo "小于或等于" || echo "否"

    小于或等于

    7)提取当前登录的用户数,比较是否超过5。

    [root@svr5 ~]# who | wc -l   //确认已登录的用户数

    4

    [root@svr5 ~]# N=$(who | wc -l)   //赋值给变量N

    [root@svr5 ~]# [ $N -gt 5 ] && "超过了" || echo "没超过"

    没超过

    上述赋值给变量N及与5比较的操作,可以简化为如下形式:

    [root@svr5 ~]# [ $(who | wc -l) -gt 5 ] && "超过了" || echo "没超过"

    没超过

    步骤四:字符串匹配

    1)== 比较两个字符串是否相同

    检查当前用户是否为root。

    当root用户执行时:

    [root@svr5 ~]# [ $USER == "root" ] && echo "YES" || echo "NO"

    YES

    当普通用户执行时:

    [zengye@svr5 ~]$ [ $USER == "root" ] && echo "YES" || echo "NO"

    NO

    2)!= 比较两个字符串是否不相同

    检查当前用户,如果不是root,显示“禁止访问”,如果是root,显示“欢迎”。

    当普通用户执行时:

    [zengye@svr5 ~]$ [ $USER != "root" ] && echo "禁止访问" || echo "欢迎"

    禁止访问

    当root用户执行时:

    [root@svr5 ~]# [ $USER != "root" ] && echo "禁止访问" || echo "欢迎"

    欢迎

    3)-z 检查变量的值是否未设置(空值)

    [root@svr5 ~]# var1="Tarena" ; var2=""

    [root@svr5 ~]# [ -z "$var1" ] && echo "空值" || echo "非空值"

    非空值

    [root@svr5 ~]# [ -z $var2 ] && echo "空值" || echo "非空值"

    空值   //变量var2已设置,但无任何值,视为空

    [root@svr5 ~]# var2=" "   //为变量var2赋值为一个空格

    [root@svr5 ~]# [ -z "$var2" ] && echo "空" || echo "非空"

    非空   //空格视为有效字符,所以非空

    [root@svr5 ~]# [ -z $var3 ] && echo "空值" || echo "非空值"

    空值    //变量var3未设置,也视为空

    4)-n 检查变量的值是否不为空,等效于 ! -z

    [root@svr5 ~]# [ -n "$var2" ] && echo "不为空" || echo "为空"

    不为空

    [root@svr5 ~]# [ -n "$var3" ] && echo "不为空" || echo "为空"

    为空

    步骤五:多个条件/操作的逻辑组合

    1)&&,逻辑与

    给定条件必须都成立,整个测试结果才为真。

    检查变量X的值是否大于10,且小于30:

    [root@svr5 ~]# X=20   //设置X变量的值为20

    [root@svr5 ~]# [ $X -gt 10 ] && [ $X -lt 30 ] && echo "YES"

    YES

    多个条件组合时,可以使用 [[ .. ]] 界定,比如上述测试可以改为如下:

    [root@svr5 ~]# [[ $X -gt 10 && $X -lt 30 ]] && echo "YES"

    YES

    2)||,逻辑或

    只要其中一个条件成立,则整个测试结果为真。

    检查变量X的值是否小于10或者小于30:

    [root@svr5 ~]# [[ $X -lt 10 || $X -lt 30 ]] && echo "YES"

    YES

    只要/tmp/、/var/spool/目录中有一个可写,则条件成立:

    [root@svr5 ~]# [ -w "/tmp/" ] || [ -w "/var/spool/" ] && echo "OK"

    OK

    使用if选择结构 问题

    本案例要求编写3个Shell脚本,分别实现以下目标:

    检测/media/cdrom目录,若不存在则创建检测并判断指定的主机是否可ping通从键盘读取一个分数,判断成绩分档(优秀、良好、不合格) 方案

    if单分支的语法组成:

    if  条件测试

        then  命令序列

    fi

    if双分支的语法组成:

    if  条件测试

        then  命令序列1

        else  命令序列2

    fi

    if多分支的语法组成:

    if  条件测试1

        then  命令序列1

    elif  条件测试2

        then  命令序列2

        else  命令序列n

    fi

    if多分支结构实际上相当于多层if嵌套:

    if  条件测试1  ; then

        命令序列1

    else

        if  条件测试2  ; then

            命令序列2

        else

            .. ..

            命令序列n

        fi

    fi

    步骤

    实现此案例需要按照如下步骤进行。

    步骤一:检测/media/cdrom目录,若不存在则创建

    1)分析任务需求

    可能需要执行的操作:创建文件夹/media/cdrom。

    执行该操作需要满足的条件:/media/cdrom目录经检测后发现不存在。

    2)采用if单分支结构整理实现思路

    if  [ 目录/media/cdrom不存在 ]  ;  then

        创建文件夹/media/cdrom

    fi

    3)根据实现思路编写脚本、替换为相关操作语句

    [root@svr5 ~]# vim chkmountdir.sh

    #!/bin/bash

    MOUNT_DIR="/media/cdrom/"

    if [ ! -d $MOUNT_DIR ]

    then

        mkdir -p $MOUNT_DIR

    fi

     

    [root@svr5 ~]# chmod +x chkmountdir.sh   //添加可执行权限

    4)测试、验证脚本功能

    [root@svr5 ~]# ls -ld /media/cdrom   //本来没有/media/cdrom目录

    ls: /media/cdrom: 没有那个文件或目录

    [root@svr5 ~]# ./chkmountdir.sh   //执行脚本

    [root@svr5 ~]# ls -ld /media/cdrom   //再检查已经有了

    drwxr-xr-x 2 root root 4096 12-11 15:16 /media/cdrom

    有了/media/cdrom文件夹以后,再次执行上述脚本,实际上不做任何有效操作:

    [root@svr5 ~]# ./chk_bakdir.sh

    [root@svr5 ~]#

    步骤二:检测并判断指定的主机是否可ping通

    1)分析任务需求

    可能需要执行的操作:操作1,提示up;操作2,提示down。

    执行操作1需要满足的条件:经检测后发现目标主机可ping通。

    执行操作2需要满足的条件:经检测后发现目标主机不能ping通。

    2)采用if双分支结构整理实现思路

    if  [ 目标主机能ping通 ]  ;  then

        提示“Host .. .. is up.

    else

        提示“Host .. .. is down.

    fi

    3)根据实现思路编写脚本、替换为相关操作语句

    使用ping命令检测目标主机时,人工可直接判断反馈结果,而脚本却不方便。但是当ping测试成功时,执行状态$?的值为0;而ping测试失败时,$?的值不为0。因此在Shell脚本中可以利用这一点来判断ping目标主机的成败。

    为了节省ping测试时间,可以只发送3个测试包(-c 3)、缩短发送测试包的间隔秒数(-i 0.2)、等待反馈的超时秒数(-W 3)。比如,检查可ping通的主机:

    [root@svr5 ~]# ping -c 3 -i 0.2 -W 3 192.168.4.5

    PING 192.168.4.5 (192.168.4.5) 56(84) bytes of data.

    64 bytes from 192.168.4.5: icmp_seq=1 ttl=64 time=0.131 ms

    64 bytes from 192.168.4.5: icmp_seq=2 ttl=64 time=0.076 ms

    64 bytes from 192.168.4.5: icmp_seq=3 ttl=64 time=0.073 ms

     

    --- 192.168.4.5 ping statistics ---

    3 packets transmitted, 3 received, 0% packet loss, time 402ms

    rtt min/avg/max/mdev = 0.073/0.093/0.131/0.027 ms

     

    [root@svr5 ~]# echo $?   //执行状态表示成功

    0

    检查无法ping通的主机:

    [root@svr5 ~]# ping -c 3 -i 0.2 -W 3 192.168.4.50

    PING 192.168.4.50 (192.168.4.50) 56(84) bytes of data.

    From 192.168.4.55 icmp_seq=1 Destination Host Unreachable

    From 192.168.4.55 icmp_seq=2 Destination Host Unreachable

    From 192.168.4.55 icmp_seq=3 Destination Host Unreachable

     

    --- 192.168.4.50 ping statistics ---

    3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 3001ms

    pipe 3

     

    [root@svr5 ~]# echo $?   //执行状态表示失败

    1

    于是,判断ping检测的执行状态,采用if双分支结构,目标主机的地址可以通过位置变量$1来提供。脚本编写参考如下:

    [root@svr5 ~]# 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

     

    [root@svr5 ~]# chmod +x pinghost.sh

    4)测试、验证脚本功能

    [root@svr5 ~]# ./pinghost.sh 192.168.4.5

    Host 192.168.4.5 is up.

    [root@svr5 ~]# ./pinghost.sh 192.168.4.50

    Host 192.168.4.50 is down.

    步骤三:从键盘读取一个分数,判断成绩分档(优秀、良好、不合格)

    1)分析任务需求

    从键盘读取分数可以采用read语句实现。

    而成绩分档的依据为:85~100分为“优秀”、70~84分为“合格”、低于70分为“不及格”。可以采用if语句判断分数值的范围,并分为三个分支,分别显示结果。

    实现思路如下:

    if [ 分数在85~100之间 ] ; then

        echo ".. .. 分!优秀"

    elif [ 分数在75~84之间 ] ; then

        echo ".. .. 分,合格"

    else

        echo ".. .. 分?不合格"

    fi

    2)根据实现思路编写脚本、替换为相关操作语句

    脚本编写参考如下:

    [root@svr5 ~]# vim gradediv.sh

    #!/bin/bash

    read -p "请输入分数(0-100):" FS

    if [ $FS -ge 85 ] && [ $FS -le 100 ] ; then

        echo "$FS 分!优秀"

    elif [ $FS -ge 70 ] && [ $FS -le 84 ] ; then

        echo "$FS 分,合格"

    else

        echo "$FS 分?不合格"

    fi

     

    [root@svr5 ~]# chmod +x gradediv.sh

    3)测试、验证脚本

    [root@svr5 ~]# ./gradediv.sh

    请输入分数(0-100):74

    74 分,合格

    [root@svr5 ~]# ./gradediv.sh

    请输入分数(0-100):68

    68 分?不合格

    [root@svr5 ~]# ./gradediv.sh

    请输入分数(0-100):87

    87 分!优秀

     

     


    最新回复(0)