老男孩shell教程 (6-10章节)

shell脚本的条件测试与比较

在bash编程中,条件测试常用的语法形式如下表:

说明⚠️:

  • 语法1中的test命令和语法2中的[]是等价的。
  • 语法3中的[[]]双中括号为扩展的test命令。
  • 语法4中的(())常用于计算。
  • 在双中括号[[]]中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。
  • &&、||、>、<等操作符可以应用于双中括号{[[]]中,但不能应用于单中括号[]中,在[]中一般用-a、-o、-gt(用于整数)、-lt(用于整数)代替上述操作符。
  • 对于整数的关系运算,也可以是使用shell的算术符(())。

条件测试简单语法与运用

test使用

test语法格式:test<测试表达式>

测试文件file是否存在,代码如下:
[root@wtf tmp]# test -s file.txt && echo "true" || echo "false"
false
[root@wtf tmp]# test -s seq.txt && echo "true" || echo "false"
true

说明⚠️:

test命令若执行成功(为真),则执行 && 后面的命令,反之执行 || 后面的命令。

测试字符串的长度是否为0,代码如下:
[root@wtf tmp]# test -z "wutf" && echo "true" || echo "false"
false
[root@wtf tmp]# test -z " " && echo "true" || echo "false"
false
[root@wtf tmp]# test -z "" && echo "true" || echo "false"
true

[](中括号)使用

语法格式:[ 条件测试表达式 ] ==>中括号两端要有空格

测试file文件是否存在,代码如下:

[root@wtf tmp]# [ -f file.txt ] && echo "true" || echo "false"
false
[root@wtf tmp]# [ -f seq.txt ] && echo "true" || echo "false"
true

[[]](双中括号)使用 (了解即可)

语法格式:[[ 条件测试表达式 ]] ==>双中括号里的两端也要有空格

文件测试表达式

常用的文件测试操作符:

测试时变量的特殊写法及问题

用 [] 测试变量时,如果被测试的变量不加双引号,那么测试结果可能会是不正确的,如下:

[root@wtf tmp]# unset wutf
[root@wtf tmp]# echo $wutf

[root@wtf tmp]# [ -f $wutf ] && echo 1 || echo 0  ## $wutf是不存在的,应该返回值0,这就逻辑不对了。
1
[root@wtf tmp]# [ -f "$wutf" ] && echo 1 || echo 0
0

如果是文件实体路径,那么加引号与不加引号的结果是一样的:

[root@wtf tmp]# [ -f /etc/services ] && echo 1 || echo 0
1
[root@wtf tmp]# [ -f "/etc/services" ] && echo 1 || echo 0
1

特殊条件测试表达式案例

以下写法适用于所有的条件测试表达式,是工作中比较常用的替代if语句的方法。

当条件1成立时,同时执行命令1、命令2、命令3,如下:

上面的判断,相当于下面的if语句的效果,如下:

字符串测试表达式

字符串测试操作符,如下:

整数二元比较操作符

整数二元比较操作符使用参考,如下:

针对上述符号的特殊说明⚠️:

  • “=”和“!=”也可以在[ ]中作比较使用,但在[ ] 中使用包含“>”和“<”的符号时,需要转义,有时不转义,虽然语法不会报错,但是结果可能不对。
  • 也可以在[[]]中使用包含“-gt”和“lt”的符号,但是不建议这样使用。
  • 比较符号两端也要有空格。

逻辑操作符

逻辑操作符介绍如下:

对于上述操作符,说明⚠️如下几点:

  • “&&”和“||”逻辑操作符号可用于[[]]和(())中,也可以在外部连接多个[]
  • 在[]和[[]]的两端,必须要有空格,但是对于(())不需要

测试表达式test、[]、[[]]、(())的区别总结

单分支 if 条件语句实践

Linux7版本:

#!/bin/bash

FreeMem1=$(free -m|awk 'NR==2 {print $4}')
FreeMem2=$(free -m|awk 'NR==2 {print $6}')
FreeMem3=$(free -m|awk 'NR==2 {print $7}')
# 系统剩余内存
FreeMem=$(($FreeMem1+$FreeMem2+$FreeMem3))
# 邮件正文内容
CHARS="Current memory is $FreeMem"
# 判断内存是否达到报警阀值
if [ $FreeMem -lt 600 ];then
    # 屏幕输出显示,并写入文件
    echo $FreeMem|tee /tmp/message.txt
    # 发送报警邮件
    mail -s "$(date +%F-%T)$CHARS" 175xxxx3745@163.com < /tmp/message.txt
fi

Linux6版本:

#!/bin/bash
# 系统剩余内存
FreeMem=$(free -m|awk 'NR==3 {print $NF}') # 等价于 $(free -m |awk 'NR==3 {print $4}')
echo $FreeMem
# 邮件正文内容
CHARS="Current memory is $FreeMem"
# 判断内存是否达到报警阀值
if [ $FreeMem -lt 600 ];then
    # 屏幕输出显示,并写入文件
    echo $FreeMem|tee /tmp/message.txt
    # 发送报警邮件
    mail -s "$(date +%F-%T)$CHARS" 175xxxx3745@163.com < /tmp/message.txt
 fi

说明⚠️:

配置下系统mail,/etc/mail.rc,如下形式:

set from=175xxxx3745@163.com # 设置发件人信息,注:如果使用163邮箱,发件人信息必须设置和实际邮箱号一致,不然无法发送。
set smtp=smtp.163.com # 设置邮件服务器
set smtp-auth-user='175xxxx3745' # 设置验证用户名
set smtp-auth-password='xxxxwtf'  # 设置验证密码
set smtp-auth=login # 可忽略

多分支 if 条件语句实践

开发rsync服务的启动脚本

脚本要求和说明

rsync 基本信息
/etc/rsyncd.conf # rsync配置文件
rsync --daemon #启动命令
netstat -lnp|grep 873 #服务端口
pkill rsync #停止rsync服务

说明⚠️:

rsync服务启动脚本
# cat rsync_test.sh
#!/bin/bash
# 判断输入的参数个数是否为1个
if [ $# -ne 1 ];then
    echo $"USAGE:$0 {start|stop|restart}"
    exit 1
fi
# 对输入参数行为进行判断
if [ "$1" = "start" ];then
    rsync --daemon
    sleep 2

    if [ $(netstat -lnp|grep rsync|wc -l) -ge 1 ];then
        echo "rsyncd is started"
        exit 0
    fi
elif [ "$1" = "stop" ];then
    killall rsync &>/dev/null
    sleep 2
    if [ $(netstat -lnp|grep rsync|wc -l) -eq 0 ];then
        echo "rsyncd is stopped"
        exit 0
    fi
elif [ "$1" = "restart" ];then
    killall rsync
    sleep 1
    killpro=$(netstat -lnp|grep rsync|wc -l)
    rsync --daemon
    startpro=$(netstat -lnp|grep rsync|wc -l)
    if [ $killpro -eq 0 -a $startpro -ge 1 ];then
        echo "rsyncd is restart"
        exit 0
    fi
else
    echo $"USAGE:$0 {start|stop|restart}"
    exit 1
fi
脚本执行情况
[root@wtf tmp]# sh rsync_test.sh status
USAGE:rsync_test.sh {start|stop|restart}
[root@wtf tmp]# sh rsync_test.sh
USAGE:rsync_test.sh {start|stop|restart}
[root@wtf tmp]# sh rsync_test.sh stop
rsyncd is stopped
[root@wtf tmp]# sh rsync_test.sh start
rsyncd is started
[root@wtf tmp]# sh rsync_test.sh restart
rsyncd is restart
[root@wtf tmp]# netstat -lnp|grep 873
tcp        0      0 0.0.0.0:873             0.0.0.0:*               LISTEN      10296/rsync
tcp6       0      0 :::873                  :::*                    LISTEN      10296/rsync

Shell 函数知识和实践

Linux 系统别名

# alias network="systemctl status network"
# network
● network.service - LSB: Bring up/down networking
   Loaded: loaded (/etc/rc.d/init.d/network; bad; vendor preset: disabled)
   Active: active (exited) since 二 2018-10-23 11:16:46 CST; 3h 7min ago

Shell 函数基本实践

创建一个简单的函数
# cat function_test1.sh
#!/bin/bash
# 定义一个wutf函数
wutf(){
    echo "this is a test function"
}
#调用函数
wutf
运行这个脚本
# sh function_test1.sh
this is a test function
分离函数体和执行函数的脚本文件

首先建立函数库脚本:

# cat >>/etc/init.d/test<<EOF  # 追加如下内容至/etc/init.d/functions文件中
> #!/bin/bash
> test(){
>     echo "this company name is datagrand"
> }
> EOF

赋予函数库文件的执行权限:

chmod 755 /etc/init.d/test

开发执行脚本以调用上述函数:

# cat function_test2.sh
#!/bin/bash

[ -f /etc/init.d/test ] && source /etc/init.d/test || exit 1
# 调用函数
test

执行函数:

# sh function_test2.sh
this company name is datagrand
带参数的Shell函数
定义函数库文件:

# cat /etc/init.d/test2
#!/bin/bash

data(){
    echo "this company name is $1"
}

执行脚本调用上面的函数:

# cat function_test3.sh

#!/bin/bash

[ -f /etc/init.d/test2 ] && source /etc/init.d/test2 || exit 1
# 调用函数
test daguan

执行脚本:

# sh function_test3.sh
this company name is daguan
使用位置参数的Shell函数
定义函数库文件:

# cat /etc/init.d/test2
#!/bin/bash

data(){
    echo "this company name is $1"
}

执行脚本调用上面的函数:
# cat function_test4.sh

#!/bin/bash

[ -f /etc/init.d/test2 ] && source /etc/init.d/test2 || exit 1
# 调用函数
test $1

执行脚本:

# sh function_test4.sh wutf
this company name is wutf

case 条件语句的应用实践

case 语句的应用

常被应用于实现系统服务启动脚本等企业应用场景中

case 条件语句的语法

case "变量" in
    值1)
        指令1
        ;;
    值2)
        指令2
        ;;
    *)
        指令3
esac

中文形象表述:

case 条件语句的执行流程逻辑图:

case 条件语句与函数结合实践

代码如下:
# cat case_test2.sh
#!/bin/bash
#定义颜色变量
RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
RES='\E[0m'
#定义帮助信息函数
function usage(){
    echo "USAGE: $0 {1|2|3|4}"
    exit 1
}
#定义菜单函数
function menu(){
    cat <<EOF
    1.apple
    2.pear
    3.banana
EOF
}
#定义选项执行函数
function chose(){
    read -p "Please input your choice: " fruit
    case "$fruit" in
        1)
            echo -e "${RED_COLOR}apples${RES}"
            ;;
        2)
            echo -e "${GREEN_COLOR}peer${RES}"
            ;;
        3)
            echo -e "${YELLOW_COLOR}banana${RES}"
            ;;
        *)
            usage
esac
}
#定义主函数
function main(){
    menu
    chose
}
main
代码执行情况
[root@wtf tmp]# sh case_test2.sh
    1.apple
    2.pear
    3.banana
Please input your choice: 1
apples                          #红色字体
[root@wtf tmp]# sh case_test2.sh
    1.apple
    2.pear
    3.banana
Please input your choice: 2
peer                            #绿色字体
[root@wtf tmp]# sh case_test2.sh
    1.apple
    2.pear
    3.banana
Please input your choice: 3
banana                          #×××字体
[root@wtf tmp]# sh case_test2.sh
    1.apple
    2.pear
    3.banana
Please input your choice: 4
USAGE: case_test2.sh {1|2|3|4}

case 生产环境实际运用

现在好多企业喜欢使用Svn,随着公司业务和人员的增加,运维人员需要对公司的svn平台有个比较直观的认识,如人员的增删,人员查询等。

下面的代码的作用是:一键添加、查找和查找人员信息

代码如下:
#!/bin/bash
#create by wutf

#加载系统函数库
. /etc/init.d/functions
#定义文件路径
FILE_PATH="/tmp/passwd"
[ ! -f $FILE_PATH ] && touch  $FILE_PATH
#定义帮助函数
function usage(){
    echo "USAGE: $0 {-add|-del|-search} username"
    exit 1
}
#定义判断执行账户函数
function user(){
    if [ $UID -ne 0 ];then
        echo "you are not root"
        exit 2
    fi
}
#定义执行脚本参数的个数
function arg_num(){
    if [ $# -ne 2 ];then
        usage
        exit 3
    fi
}
#定义case语句判断
function case_info(){
    case "$1" in
        -a|-add)
            shift
            if grep "^$1" $FILE_PATH >/dev/null 2>&1
                then
                    action "svnuser $1 is exist" /bin/false
                    exit 4
            else
                /bin/cp $FILE_PATH $FILE_PATH.$(date +%F%T)
                htpasswd $FILE_PATH $1
                [ $? -eq 0 ] && action "Add $1 is success" /bin/true
            fi
            ;;
        -d|-del)
            shift
            if [ $(grep "$1" $FILE_PATH | wc -l) -lt 1 ];then
                action "svnuser $1 is not exist" /bin/false
                exit 5
            else
                /bin/cp $FILE_PATH $FILE_PATH.$(date +%F%T)
                sed -i "/^$1/d" $FILE_PATH
                [ $? -eq 0 ] && action "Del $1" /bin/true || exit 6
            fi
            ;;
        -s|-search)
            shift
            if [ $(grep "^$1" $FILE_PATH | wc -l) -lt 1 ];then
                echo "svnuser $1 is not exist"
                exit 7
            else
                echo "svnuser $1 is exist"
            fi
            ;;
        *)
            exit 8
    esac
}

#定义主函数
function main(){
    user
    arg_num $1 $2
    case_info $1 $2
}
main $*
代码执行情况
# sh management_svn.sh -a wutf
New password:
Re-type new password:
Adding password for user wutf
Add wutf is success                                        [  确定  ]
# sh management_svn.sh -s wutf
svnuser wutf is exist
# sh management_svn.sh -s wangyiwen
svnuser wangyiwen is not exist
# sh management_svn.sh -d wangyiwen
svnuser wangyiwen is not exist                             [失败]
# sh management_svn.sh -d wutf
Del wutf                                                   [  确定  ]

while 循环和 until 循环的应用实践

Shell脚本语言的循环语句常见的有while、until、for 和 select 循环语句。

当型和直到型循环语法

while 循环语句

基本语法:

while <条件表达式>
do
    指令。。 #注意代码缩进
done

说明⚠️:

while循环语句会对紧跟在while命令后的条件表达式进行判断,如果该条件表达式成立,则执行while循环体里的命令或语句(即语法中do和done之间的指令),

每一次执行到done时就会重新判断while条件表达式是否成立,直到条件表达式不成立时才会跳出while循环体。

如果一开始条件表达式就不成立,那么程序就不会进入循环体中执行命令。

可用手机充值形象说明:

while循环执行流程对应的逻辑图:

until循环语句

基本语法:

until <条件表达式>
do
    指令。。
done

说明⚠️:

until表达式是在条件表达式不成立时,进入循环执行指令,条件表达式成立时,终止循环。

until应用场景很罕见,了解即可。

当型和直到型循环实战

shell中两个休息命令:

sleep 1
usleep 1000000
均表示休息1秒
while true #可以使用while [ 1 ] 代替
do
    uptime
    sleep 2
done

关于后台运行的命令

让进程在后台可靠运行的几种方法

让shell脚本在后台运行的用法和说明

while 用于监控网站

开发要求

使用while守护进程的方式监控网站,每隔10秒确定一次网站是否正常

脚本代码
# cat while_test2.sh
#!/bin/bash
#引入系统函数库
. /etc/init.d/functions
#判断参数的数量
if [ $# -ne 1 ];then
    echo "usage $0 url"
    exit 1
fi
#定义while循环
while true
do
    if [ $(curl -o /dev/null --connect-timeout 5 -s -w "%{http_code}" $1|egrep -w "200|301|302"|wc -l) -ne 1 ];then
        action "this is URL $1 not normal" /bin/false
    else
        action "this is URL $1 normal" /bin/true
    fi
    sleep 10
done
脚本执行情况
# sh while_test2.sh www.wutf.com
this is URL www.wutf.com normal                            [  确定  ]
this is URL www.wutf.com not normal                        [失败]
this is URL www.wutf.com normal                            [  确定  ]

# sh while_test2.sh daguan.com
this is URL daguan.com not normal                          [失败]
this is URL daguan.com not normal                          [失败]

写在最后

喜欢读技术书籍,喜欢做读书笔记,以上为本人在读《跟老男孩学Linux运维之shell编程实战》这本书时的笔记,如有任何版权问题,请联系留言。