shell中的函数
- 把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段代码时直接调用这个小单元的名字即可。
- 函数就是一个子shell,就是一个代码段,定义完函数就可以引用它
- 格式: function 后是函数的名字,并且 function 这个单词是可以省略掉的;花括号{} 里面为具体的命令
格式: function f_name() { command } 函数必须要放在最前面
示例1:这个函数是用来打印参数
[root@aminglinux shell]# vim fun1.sh [root@aminglinux shell]# cat fun1.sh #!/bin/bash input(){ echo $1 $2 $0 $# } input 1 a b 9
[root@aminglinux shell]# sh -x fun1.sh + input 1 a b 9 + echo 1 a fun1.sh 4 1 a fun1.sh 4
函数,可以直接写在脚本内,相当于直接调用
內建变量
$1 第一个参数
$2 第二个参数
...
~
$# 参数名字
$0 总共有几个参数
[root@aminglinux shell]# vim fun1.sh [root@aminglinux shell]# cat fun1.sh #!/bin/bash input(){ echo $1 $2 $0 $# } input $1 $2 [root@aminglinux shell]# sh fun1.sh fun1.sh 0 [root@aminglinux shell]# sh fun1.sh 1 4 1 4 fun1.sh 2
**示例2:**用于定义加法的函数,shell中定义的函数,必须放在上面
- 在shell里面需要优先定义函数,比如在调用这个函数的时候,函数还没有定义,就会报错
- 在想要调用哪一个函数,就必须在调用语句之前,先定义这个函数
[root@aminglinux shell]# vim fun2.sh [root@aminglinux shell]# cat fun2.sh #!/bin/bash sum() { s=$[$1+$2] #定义变量s = $1+$2 /其中 $1为第一个参数,$2为第二个参数 echo $s } sum 2 6 #输出 第一个参数和第二个参数 [root@aminglinux shell]# sh -x fun2.sh + sum 2 6 + s=8 + echo 8 8
**例3:**显示IP,输入网卡的名字,然后显示网卡的IP
[root@aminglinux shell]# vim fun3.sh [root@aminglinux shell]# cat fun3.sh #!/bin/bash ip() { ifconfig |grep -A1 "$eth: " |awk '/inet/ {print $2}' #查看网卡,过滤出ens33及下面的一行,匹配inet行并打印出第二段 } read -p "Please input the eth name: " eth myip=`ip $eth` echo "$eth address is $myip"
- grep -A1 "ens33" 过滤显示出关键词及关键词下的一行
[root@aminglinux shell]# ifconfig |grep -A1 "ens33" ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.222.110 netmask 255.255.255.0 broadcast 192.168.222.255 [root@aminglinux shell]# ifconfig |grep -A1 "ens33"|tail -1 inet 192.168.222.110 netmask 255.255.255.0 broadcast 192.168.222.255 [root@aminglinux shell]# ifconfig |grep -A1 "ens33"|tail -1|awk '{print $2}' 192.168.222.110
**案例4:**判定是否为本机的网卡,判定输入的网卡是否有IP
[root@aminglinux shell]# vim fun4.sh [root@aminglinux shell]# cat fun4.sh #!/bin/bash #coding:utf8 ip() { a=`ifconfig |grep -A1 "$eth: " |tail -1 |awk '{print $2}'` if [ -z "$a" ] then echo $eth echo "没有这个网卡名" exit fi echo $a } read -p "请输入你的网卡名字: " eth myip=`ip $eth` echo "$eth address is $myip"
[root@aminglinux shell]# sh -x fun4.sh + read -p '请输入你的网卡名字: ' eth 请输入你的网卡名字: ens33 ++ ip ens33 +++ grep -A1 'ens33: ' +++ awk '{print $2}' +++ tail -1 +++ ifconfig ++ a=192.168.222.110 ++ '[' -z 192.168.222.110 ']' ++ echo 192.168.222.110 + myip=192.168.222.110 + echo 'ens33 address is 192.168.222.110' ens33 address is 192.168.222.110 [root@aminglinux shell]# sh fun4.sh 请输入你的网卡名字: ens 38 ens 38 address is ens 38 没有这个网卡名
shell中的数组
-
一串数字或一串字符串的形式,形成一个变量
-
shell中的数组1
定义数组 a=(1 2 3 4 5); echo ${a[@]} echo ${#a[@]} 获取数组的元素个数 echo ${a[2]} 读取第三个元素,数组从0开始 echo ${a[*]} 等同于 ${a[@]} 显示整个数组
-
数组赋值
a[1]=100; echo ${a[@]} a[5]=2; echo ${a[@]} 如果下标不存在则会自动添加一个元素
-
数组的删除
uset a; unset a[1]
-
shell中的数组2
-
数组分片
a=(`seq 1 10`) echo ${a[@]:0:3} 从第一个元素开始,截取3个 echo ${a[@]:1:4} 从第二个元素开始,截取4个 echo ${a[@]:0-3:2} 从倒数第3个元素开始,截取2个
-
数组替换
echo ${a[@]/3/100} a=(${a[@]/3/100})
-
定义数组:
[root@aminglinux shell]# a=(1 2 3 4 5 6) [root@aminglinux shell]# echo ${a[@]} //@、*都可以打印数组所有元素 1 2 3 4 5 6
- 取数组的元素:
[root@aminglinux shell]# echo ${a[2]} //数组第三个元素,从0算起:a[0] a[1] a[2] 3 [root@aminglinux shell]# echo ${#a[@]} //数组的元素个数 6
- 数组元素赋值、覆盖
[root@aminglinux shell]# a[1]=100 [root@aminglinux shell]# echo ${a[@]} 1 100 3 4 5 6 [root@aminglinux shell]# a[6]=2 [root@aminglinux shell]# echo ${a[@]} //如果下标不存在,则自动添加一个元素,存在则覆盖 1 100 3 4 5 6 2
-
删除数组:
-
[root@aminglinux shell]# unset a[1]
-
[root@aminglinux shell]# echo ${a[@]}
-
1 3 4 5 6 2
-
[root@aminglinux shell]# unset a
-
[root@aminglinux shell]# echo ${a[@]}
-
[root@aminglinux shell]#
-
数组分片:截取某几个元素
[root@aminglinux shell]# a=(`seq 1 10`) [root@aminglinux shell]# echo ${a[@]:0:3} //从第1个元素开始,取3个元素 1 2 3 [root@aminglinux shell]# echo ${a[@]:1:4} //从第2个元素开始,取4个元素 2 3 4 5 [root@aminglinux shell]# echo ${a[@]:0-3:2} //从倒数第3个元素开始,取2个元素 8 9
- 数组替换
[root@aminglinux shell]# echo ${a[@]/3/100} 1 2 100 4 5 6 7 8 9 10 [root@aminglinux shell]# a=(${a[@]/3/100}) //直接赋值(需要用括号括起来) [root@aminglinux shell]# echo ${a[@]} 1 2 100 4 5 6 7 8 9 10
告警系统需求分析
- 需求:使用shell定制各种个性化告警工具,但需要统一化管理、规范化管理。
- 思路:指定一个脚本包,包含主程序、子程序、配置文件、邮件引擎、输出日志等。
- 主程序:作为整个脚本的入口,是整个系统的命脉。
- 配置文件:是一个控制中心,用它来开关各个子程序,指定各个相关联的日志文件。
- 子程序:这个才是真正的监控脚本,用来监控各个指标。
- 邮件引擎:是由一个python程序来实现,它可以定义发邮件的服务器、发邮件人以及发件人密码
- 输出日志:整个监控系统要有日志输出。
- 要求:我们的机器角色多种多样,但是所有机器上都要部署同样的监控系统,也就说所有机器不管什么角色,整个程序框架都是一致的,不同的地方在于根据不同的角色,定制不同的配置文件。
** 程序架构:**
- bin下是主程序
- conf下是配置文件
- shares下是各个监控脚本
- mail下是邮件引擎
- log下是日志。
告警系统主脚本
-
定义监控系统的各个目录,然后再去定义主脚本,因为是分布式的,所以需要每一台机器都需要定义,事先创建好各个脚本和各个目录,随后脚本直接拷贝过去即可,然后再去做一些更改
-
所有的shell脚本放到 /usr/local/sbin/ 目录下,方便查找
-
切换到 /usr/local/sbin/ 目录下,并创建子目录
[root@aminglinux ~]# cd /usr/local/sbin/ [root@aminglinux sbin]# mkdir mon [root@aminglinux sbin]# cd mon [root@aminglinux mon]# mkdir bin conf shares mail log [root@aminglinux mon]# ls bin conf log mail shares
- 切换到 bin 目录下(主脚本放在 bin 目录下,主脚本作为一个入口,应该去判断配置文件,查看某监控项目是否需要监控,还需调用各个需要监控的子脚本)
[root@aminglinux mon]# cd bin [root@aminglinux bin]# vim main.sh #!/bin/bash export send=1 //shell环境变量,是否发送邮件的开关 export addr=`/sbin/ifconfig |grep -A1 "ens33: "|awk '/inet/ {print $2}'` //把监控的机器的ip地址过滤出来 dir=`pwd` //获取当前脚本的目录,保证后面能够找到配置文件和子脚本 last_dir=`echo $dir|awk -F'/' '{print $NF}'` //只需要最后一级目录名 if [ $last_dir == "bin" ] || [ $last_dir == "bin/" ] //判断目的是保证执行脚本的时候,我们在bin目录里,不然监控脚本、邮件和日志很有可能找不到 then conf_file="../conf/mon.conf" //配置文件的路径 else echo "you shoud cd bin dir" exit fi exec 1>>../log/mon.log 2>>../log/err.log //记录监控主脚本运行的输出和错误信息到文件 echo "`date +"%F %T"` load average" /bin/bash ../shares/load.sh //执行负载监控子脚本 if grep -q 'to_mon_502=1' $conf_file //先检查配置文件中是否需要监控502 then export log=`grep 'logfile=' $conf_file |awk -F '=' '{print $2}' |sed 's/ //g'` //过滤web的访问日志的路径给环境变量log /bin/bash ../shares/502.sh //执行502监控子脚本 fi
告警系统配置文件
- 配置文件路径在: /mon/conf/mon.conf
[root@aminglinux mon]# cd conf/ [root@aminglinux conf]# vim mon.conf ##mon.conf // to config the options if to monitor // 定义mysql的服务器地址、端口以及user、password to_mon_cdb=0 //0 or 1, default 0,0 not monitor, 1 monitor 是否监控mysql db_ip=10.20.3.13 db_port=3315 db_user=username db_pass=passwd to_mon_httpd=0 // httpd 如果是1则监控,为0不监控 to_mon_php_socket=0 // php 如果是1则监控,为0不监控 to_mon_502=1 // http_code_502 logfile=/data/log/xxx.xxx.com/access.log //需要定义访问日志的路径 to_mon_request_count=0 //request_count数监控 req_log=/data/log/www.discuz.net/access.log //定义访问日志路径以及域名 domainname=www.discuz.net
- 把请求日志摘出来的目的,你要考虑到要想把shell写得规范化,标准化,那你肯定要考虑监控的机器肯定不止1台;要想要让脚本通用,兼容性很强,就需要把所有需要监控的服务的日志都载入到配置文件中,改动起来方便,省得后期改动起来一个一个的对应脚本去修改,就太麻烦了
告警系统监控项目
- 定义子脚本,就是监控项目
- 系统负载的子脚本:/mon/shares/load.sh
[root@aminglinux conf]# cd ../shares/ [root@aminglinux shares]# vim load.sh #! /bin/bash load=`uptime |awk -F 'average:' '{print $2}'|cut -d',' -f1|sed 's/ //g' |cut -d. -f1` //过滤系统一分钟的负载 if [ $load -gt 10 ] && [ $send -eq "1" ] //负载大于10,发送邮件 then echo "$addr `date +%T` load is $load" >../log/load.tmp //记录负载的数值到文件 /bin/bash ../mail/mail.sh xxx@163.com "$addr\_load:$load" `cat ../log/load.tmp` //发送邮件动作:三个参数:收件人、主题、邮件内容 fi echo "`date +%T` load is $load"
web服务状态码502的监控脚本:/mon/shares/502.sh
[root@aminglinux shares]# vim 502.sh #! /bin/bash 502.sh d=`date -d "-1 min" +%H:%M` //记录上一分钟的时间 c_502=`grep :$d: $log |grep ' 502 '|wc -l` //统计上一分钟,502状态码出现的次数 if [ $c_502 -gt 10 ] && [ $send == 1 ] //一分钟出现大于10次 then echo "$addr $d 502 count is $c_502">../log/502.tmp //记录502次数写进文件保存 /bin/bash ../mail/mail.sh xxx@163.com "$addr\_502 $c_502" `cat ../log/502.tmp` //发送邮件动作 fi echo "`date +%T` 502 $c_502"
- (磁盘使用率):思路就是挨个把分区看下
- 磁盘空间的子脚本:/mon/shares/disk.sh
[root@aminglinux shares]# vim disk.sh #! /bin/bash rm -f ../log/disk.tmp //删除上一次的告警记录文件 for r in `df -h |awk -F '[ %]+' '{print $5}'|grep -v Use` //循环匹配各个分区的磁盘使用率 do if [ $r -gt 90 ] && [ $send -eq "1" ] //如果超过90%则告警,发邮件 then echo "$addr `date +%T` disk useage is $r" >>../log/disk.tmp //超过90%的告警信息写进记录文件 fi if [ -f ../log/disk.tmp ] //如果告警记录文件存在 then df -h >> ../log/disk.tmp //把所有分区信息也写入记录文件 /bin/bash ../mail/mail.sh xxx@163.com "$addr\_disk $r" `cat ../log/disk.tmp` //发邮件动作 echo "`date +%T` disk useage is No ok" else echo "`date +%T` disk useage is ok" fi done
- awk指定多个分隔符
[root@aminglinux shares]# echo "12:aa#123bb:22#ww" |awk -F '[:#]' '{print $3}' 123bb [root@aminglinux shares]# echo "12:aa#123bb:22#ww" |awk -F '[:#]' '{print NF}' 5 [root@aminglinux shares]# echo "12:aa#123bb:22##ww" |awk -F '[:#]' '{print NF}' 6 [root@aminglinux shares]# echo "12:aa#123bb:22##ww" |awk -F '[:#]+' '{print NF}' 5
告警系统邮件引擎
-
告警系统邮件引擎由两个文件组成,放在/mon/mail/目录下:mail.py、mail.sh
mail.py:是邮件的核心python脚本,邮件功能的实现 mail.sh:是告警邮件系统的shell脚本,实现了邮件收敛的功能,里面调用了mail.py脚本发送邮件的操作。
-
mail.sh:邮件收敛即同一监控项目告警,每一小时
[root@aminglinux shares]# cd ../mail/ [root@aminglinux mail]# vim mail.sh log=$1 //第一个参数$1:是收件人邮箱 t_s=`date +%s` //告警触发时间的时间戳 t_s2=`date -d "2 hours ago" +%s` //告警触发时间的前2个小时的时间戳,第一次产生告警时,用作来对比时间差值 if [ ! -f /tmp/$log ] //记录时间戳的文件 不存在 then echo $t_s2 > /tmp/$log //就创建一个记录时间戳的文件 fi t_s2=`tail -1 /tmp/$log|awk '{print $1}'` // 读取上一次最后一个告警时间戳 echo $t_s>>/tmp/$log //记录最新告警时间戳 v=$[$t_s-$t_s2] //这次告警时间戳与上一次告警时间戳的时间间隔差值 echo $v if [ $v -gt 3600 ] //如果两次告警间隔了1小时或以上了(或第一次运行脚本),才会进入发邮件的流程,老发邮件会被骂死的 then ./mail.py $1 $2 $3 //发邮件操作 echo "0" > /tmp/$log.txt //告警计数文件清0,重新开始计数 else //如果是一个小时内的第二次或以上的告警了 if [ ! -f /tmp/$log.txt ] then echo "0" > /tmp/$log.txt //没有技术文件,创建告警计数文件 fi nu=`cat /tmp/$log.txt` //获取告警次数 nu2=$[$nu+1] echo $nu2>/tmp/$log.txt //记录最新的告警次数 if [ $nu2 -gt 10 ] //10分钟内连续告警或者10次告警后,需要发邮件 then ./mail.py $1 "trouble continue 10 min $2" "$3" echo "0" > /tmp/$log.txt fi fi
- 该脚本运用于,间隔3600 故障;10分钟内故障;间歇性故障;
- 核心判断:计时、计数
运行告警系统
- 我们使用任务计划来运行这个告警系统,计划每分钟运行一次
crontab -e //编辑任务计划 * * * * * cd /usr/local/sbin/mon/bin ; bash main.sh //先进入主脚本的目录,再执行脚本
- 可以预先自己先手动执行一次脚本,用-x选项查看脚本执行过程,看有无报错
sh -x main.sh
- 监控发送邮件的部分,尽量少用空格,因为mail.py发送邮件是以 空格来定义三个参数的