文章目录
shell中的函数
函数可以理解为一个子脚本,就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段代码时直接调用这个小单元的名字即可。
// 函数格式:
function func_name(){
command_1
command_2
.....
}
# 也可以省略function
func_1(){
command_1
command_2
}
// 调用方式,函数必须在调用之前进行定义。
func_name # 直接像使用命令一样使用函数即可
func_1
示例1
// 一个求两个数之和的简单脚本
# cat 6.sh
#!/bin/bash
sum(){
echo "The Sum is: $[$1+$2]"
}
inp(){
read -p 'Input Number a: ' a
read -p 'Input Number b: ' b
sum a b
}
inp
# sh 6.sh
Input Number a: 4
Input Number b: 12
The Sum is: 16
示例2
// 列出系统所有网卡名字,交互式输入网卡名,然后显示该网卡的ip
# cat ip.sh
#!/bin/bash
eths(){
echo "All Interface list"
a=1
for i in `ifconfig | grep '^.*:\ ' | awk -F": " '{print $1}'`
do
echo -e "$a: $i\t"
((a++))
done
}
get_ip(){
ifconfig | grep -A1 "$1: " | awk -F" " '$1 == "inet" {print $2}'
}
eths
read -p 'Please Input the interface name: ' name
get_ip $name
// 测试脚本
# sh ip.sh
All Interface list
1: ens33
2: ens33:0
3: lo
Please Input the interface name: ens33:0
10.1.1.33
// 结果没问题
shell中的数组
数组是一个能保存多个元素的变量,一个元素就是一个值
// 定义数组的格式:
arr_name=(value1 value2 value3 ...)
// 调用数组,格式为:${arr_name[@]},
// 方括号中的 @ 表示所有的元素,也可以用 * 代替
// 调用某一个元素需要使用元素的下标, 下标从0开始,第一个元素下标为0,第二个元素下标为1,依次类推
// 比如调用第一个元素:${arr_name[0]}
// 遍历数组所有元素
# arr=(abc def ghi jkl mno)
# for i in ${arr[*]} ;do echo $i;done
abc
def
ghi
jkl
mno
// 获取数组的某一个元素
# echo ${arr[1]} // 下标1,表示数组的第二个元素
def
// 修改元素
# arr[1]=100
# echo ${arr[1]}
100
// 追加元素,当修改元素时指定的下标不存在时,则会将这个值自动添加到数组的最后,
# echo ${arr[*]}
abc 100 ghi jkl mno 100
# arr[9]=add
# echo ${arr[*]}
abc 100 ghi jkl mno 100 add
// 删除数组的某一个元素
# unset arr[0]
# echo ${arr[*]}
100 ghi jkl mno 100 add
// 删除整个数组
# echo ${arr[*]}
// 数组的分片
# a=(`seq 1 5`)
// 从第一个元素开始,截取3个
# echo ${a[@]:0:3}
1 2 3
// 从第二个元素开始,截取4个
# echo ${a[@]:1:4}
2 3 4 5
// 从倒数第3个元素开始,截取2个,开始的下标必须写成 0-n 的格式, 直接写 -n 会报错
# echo ${a[@]:0-3:2}
3 4
// 数组替换
# arr=(a b c d e f g h)
// 将数组中的 d 替换为 hello
# echo ${arr[@]/d/hello} //这种只是在输出的时候替换,实际的元素并没有改变
a b c hello e f g h
# echo ${arr[@]}
a b c d e f g h
// 真实替换数组的元素
# arr=${arr[@]/d/hello}
# echo ${arr[@]}
a b c hello e f g h b c d e f g h
告警系统需求分析
-
需求:使用shell定制各种个性化告警工具,但需要统一化管理、规范化管理。
-
思路:指定一个脚本包,包含主程序、子程序、配置文件、邮件引擎、输出日志等。
– 主程序:作为整个脚本的入口,是整个系统的命脉。
– 配置文件:是一个控制中心,用它来开关各个子程序,指定各个相关联的日志文件。
– 子程序:这个才是真正的监控脚本,用来监控各个指标。
– 邮件引擎:是由一个python程序来实现,它可以定义发邮件的服务器、发邮件人以及发件人密码
– 输出日志:整个监控系统要有日志输出。
# 要求:
# 我们的机器角色多种多样,但是所有机器上都要部署同样的监控系统,
# 也就说所有机器不管什么角色,整个程序框架都是一致的,
# 不同的地方在于根据不同的角色,定制不同的配置文件。
# 程序架构:
(主目录 mon)
________________________________|_____________________________________
| | | | |
bin conf shares mail log
| | | | |
[main.sh] [mon.conf] [load.sh 502.sh] [mail.py mail.sh] [mon.log err.log]
# bin下是主程序
# conf下是配置文件
# shares下是各个监控脚本
# mail下是邮件引擎
# log下是日志。
// 创建主目录和子目录
# mkdir /usr/local/sbin/mon/{bin,conf,shares,mail,log} -p
# tree /usr/local/sbin/mon
/usr/local/sbin/mon
├── bin
├── conf
├── log
├── mail
└── shares
// 创建各个文件
# cd /usr/local/sbin/mon/
# touch ./bin/main.sh ./conf/mon.conf ./shares/{load.sh,502.sh} ./mail/{mail.py mail.sh} ./log/{mon.log,err.log}
告警系统主脚本
- 定义监控系统的各个目录,然后再去定义主脚本,因为是分布式的,所以需要每一台机器都需要定义,事先创建好各个脚本和各个目录,随后脚本直接拷贝过去即可,然后再去做一些更改
- 所有的shell脚本放到 /usr/local/sbin/ 目录下,方便查找
main.sh内容
#!/bin/bash
# 是否发送邮件的开关
export send=1
# 过滤ip地址
export addr=`/sbin/ifconfig |grep -A1 "ens33: "|awk '$1=="inet" {print $2}'`
dir=`pwd`
# 只需要最后一级目录名
last_dir=`echo $dir|awk -F'/' '{print $NF}'`
# 下面的判断目的是,保证执行脚本的时候,我们在bin目录里,不然监控脚本、邮件和日志很有可能找不到
if [ $last_dir == "bin" ] || [ $last_dir == "bin/" ]; then
conf_file="../conf/mon.conf"
else
echo "you shoud cd bin dir"
exit
fi
# exec定向后面的输出到相应的日志文件
exec 1>>../log/mon.log 2>>../log/err.log
echo "`date +"%F %T"` load average"
/bin/bash ../shares/load.sh
#先检查配置文件中是否需要监控502
if grep -q 'to_mon_502=1' $conf_file; then
export log=`grep 'logfile=' $conf_file |awk -F '=' '{print $2}' |sed 's/ //g'`
/bin/bash ../shares/502.sh
fi
告警系统配置文件
- shell项目-告警系统mon.conf内容
- 配置文件(一定要放到conf目录下,名称必须为mon.conf ——PS:不能随意改动,因为在主脚本中已经设置好了)
- 定义一些开关,定义一些对应的日志路径,或者说监控mysql的用户名和密码,以及IP地址port端口等
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
db_ip=10.1.1.30
db_port=3306
db_user=username
db_pass=passwd
## httpd 如果是1则监控,为0不监控
to_mon_httpd=0
## php 如果是1则监控,为0不监控
to_mon_php_socket=0
## http_code_502 需要定义访问日志的路径
to_mon_502=0
logfile=/data/log/xxx.xxx.com/access.log
## request_count 定义日志路径以及域名
to_mon_request_count=0
req_log=/data/log/www.discuz.net/access.log
domainname=www.discuz.net
把请求日志摘出来的目的,你要考虑到要想把shell写得规范化,标准化,那你肯定要考虑监控的机器肯定不止1台;要想要让脚本通用,兼容性很强,就需要把所有需要监控的服务的日志都载入到配置文件中,改动起来方便,省得后期改动起来一个一个的对应脚本去修改,就太麻烦了
告警系统监控项目
- 定义子脚本,就是监控项目
load.sh内容
[root@123 mon]# cd shares/
[root@123 shares]# pwd
/usr/local/sbin/mon/shares
[root@123 shares]# vim load.sh
#! /bin/bash
#获取最近一分钟的系统负载
load=`uptime|sed -n 's/\(.*\)\(load average: \)\(.*$\)/\3/'p|awk -F"[,. ]" '{print $1}'`
#当系统负载超过10 并且允许发邮件的情况下,发送报警邮件
if [ $load -gt 10 ] && [ $send -eq "1" ]
then
echo "$addr `date +%T` load average is $load" >../log/load.tmp
/bin/bash ../mail/mail.sh aming_test@163.com "$addr\_load:$load" `cat ../log/load.tmp`
fi
#输出脚本运行结果
echo "`date +%T` Load is $load"
502.sh内容
- 502肯定需要一个日志,502涉及到一个访问日志,访问日志又设计到一个时间
- 因为脚本监控主脚本是的是1分钟执行一次,所以502监控,看的肯定1分钟以前访问日志的时间,信息
[root@123 shares]# vim 502.sh
#! /bin/bash
d=`date -d "-1 min" +%H:%M`
c_502=`grep :$d: $log |grep ' 502 '|wc -l`
#截取一分钟以前的时间
if [ $c_502 -gt 10 ] && [ $send == 1 ]; then
echo "$addr $d 502 count is $c_502">../log/502.tmp
/bin/bash ../mail/mail.sh $addr\_502 $c_502 ../log/502.tmp
#mail就是定义发送给谁,发送的主题,发送的内容
fi
echo "`date +%T` 502 $c_502"
disk.sh内容
- 磁盘使用率
- disk,思路就是挨个把分区看下
[root@123 shares]# vim disk.sh
#! /bin/bash
##Writen by aming##
rm -f ../log/disk.tmp
for r in `df -h |awk -F '[ %]+' '{print $5}'|grep -v Use`
#[ %]+ 以 多个,空格 或者 % 作为分隔符,+号表示一个或多个;因为系统默认是英文,所以grep -v Use 过滤掉的就是 已用
do
if [ $r -gt 90 ] && [ $send -eq "1" ]
then
echo "$addr `date +%T` disk useage is $r" >>../log/disk.tmp
fi
done
if [ -f ../log/disk.tmp ]
then
df -h >> ../log/disk.tmp
/bin/bash ../mail/mail.sh $addr\_disk $r ../log/disk.tmp
echo "`date +%T` disk useage is nook"
else
echo "`date +%T` disk useage is ok"
fi
告警系统邮件引擎
mail.sh内容
#!/bin/bash
log=$1
t_s=`date +%s`
t_s2=`date -d "2 hours ago" +%s`
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 ]
then
../mail/mail.py $1 $2 $3
echo "0" > /tmp/$log.txt
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 ]
then
../mail/mail.py $1 "trouble continue 10 min $2" "$3"
echo "0" > /tmp/$log.txt
fi
fi
mail.py内容
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
import os,sys
reload(sys)
sys.setdefaultencoding('utf8')
import getopt
import smtplib
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from subprocess import *
def sendqqmail(username,password,mailfrom,mailto,subject,content):
gserver = 'smtp.163.com'
gport = 25
try:
msg = MIMEText(unicode(content).encode('utf-8'))
msg['from'] = mailfrom
msg['to'] = mailto
msg['Reply-To'] = mailfrom
msg['Subject'] = subject
smtp = smtplib.SMTP(gserver, gport)
smtp.set_debuglevel(0)
smtp.ehlo()
smtp.login(username,password)
smtp.sendmail(mailfrom, mailto, msg.as_string())
smtp.close()
except Exception,err:
print "Send mail failed. Error: %s" % err
def main():
to=sys.argv[1]
subject=sys.argv[2]
content=sys.argv[3]
## 定义QQ邮箱的账号和密码,你需要修改成你自己的账号和密码(请不要把真实的用户名和密码放到网上公开,否则你会死的很惨)
sendqqmail('mail@163.com','passworld','mail@163.com',to,subject,content)
if __name__ == "__main__":
main()
运行告警系统
因为告警系统的作用是监控系统的负载等各项服务器功能的使用情况,所以执行告警系统,需要每分钟执行一次
crontab -e ##创建任务计划(每分钟执行一次)
* * * * * cd /usr/local/sbin/mon/bin ; bahs main.sh
注:监控发送邮件的部分,尽量少用空格,因为mail.py发送邮件是以空格来定义三个参数的(发给谁,邮件名称,内容)
定义好计划以后,重启计划,查看load的检测值是否为0
# 测试前需要给各个脚本加上执行权限
find /usr/local/sbin/mon/ -type f -name "*.sh" |xargs chmod 755
find /usr/local/sbin/mon/ -type f -name "mail.py" | xargs chmod 755
# 为了方便测试需要修改几个地方
# 1:注释load.sh脚本的获取load值的变量,添加一行 让load变量值为11
#load=`uptime|sed -n 's/\(.*\)\(load average: \)\(.*$\)/\3/'p|awk -F"[,. ]" '{print $1}'`
load=11 # 添加一行让load值等于11
# 2: 修改 mail.sh脚本中判断时间的表达式
if [ $v -gt 3600 ] 改为 if [ $v -gt 10]
# 然后进入mon/bin 目录下直接运行主脚本,看看是否可以收到邮件