shell是什么
shell是一种脚本语言 aming_linux blog.lishiming.net
可以使用逻辑判断、循环等语法
可以自定义函数
shell是系统命令的集合
shell脚本可以实现自动化运维能大大增加我们的运维效率
shell脚本结构和执行方法
开头需要加#!/bin/bash固有的格式
意思就是接下来的文件是由/bin/bash解析的
以#开头的行作为解释说明
脚本的名字以.sh结尾用于区分这是一个shell脚本
执行方法有两种:
(1)先给这个文件加一个执行权限'x'
例如:chmod a+x 1.sh 这样就可以在相对路径下用./1.sh来执行它。
(2)bash 1.sh
(3)sh 1.sh
查看脚本执行过程bash -x 1.sh
查看脚本是否语法错误 bash -n 1.sh 当一个脚本语法有错误时他会有一个提示,如果没有任何输出证明他没有错误。
date命令使用方法
data显示当前系统的日期和时间
data +%Y表示四位的年
data +%y两位的年
data +%M分钟
data +%m月份
data +%d日期
data +%D月/日/年
data +%Y%m%d年0月0日
data +%F年-月-日
data +%H小时
data +%S秒
data +%H:%M:%S 小时分钟秒=data +%T
data +%h英文月份
data +%w周几
data +%W今年的第几周
cal显示日历
data +%s时间戳
时间戳换算日期date -d @1504620492
日期换算时间戳 date +%s -d "2019-2-14 22:35:49"
查看以前的日期-d
date -d "+1day" 一天后
date -d "-1 day" 一天前
date -d "-1 month" 一月前
date -d "-1 min" 一分钟前
date +%w, date +%W 星期
shell脚本中的变量
当脚本中使用某个字符串较频繁并且字符串长度很长时就应该使用变量代替
使用条件语句时常使用变量 if [ $a -gt 1 ]; then ... ; fi
引用某个命令的结果时用变量替代 n=`wc -l 1.txt`
写和用户交互的脚本时变量也是必不可少的 read -p "Input a number: " n; echo $n 如果没写这个n可以直接使用$REPLY
内置变量 $0, $1, $2… $0表示脚本本身$1 第一个参数$2 第二个 .... $#表示参数个数
数学运算a=1;b=2; c=$(($a+$b))或者$[$a+$b]
shell中的逻辑判断
格式1:if 条件 ; then 语句; fi
-gt大于 -lt小于
也可以用大于小于号,但是必须加双括号:(($a>3));(($a<3))
举例:
a=5
如果$a大于3
则输出ok
fi结尾
格式2:if 条件; then 语句; else 语句; fi
举例:
a=5
如果a>3
则输出就ok
否则输出nook
格式3:if …; then … ;elif …; then …; else …; fi
举例:
a=4
如果$a大于4
则输出a大
在如果$a小于4
则输出大于1&&小于4
否则输出无效
逻辑判断表达式if [ $a -gt $b ]; if [ $a -lt 5 ]; if [ $b -eq 10 ]等于
-gt:大于 (>);
-lt:小于(<);
-ge:大于等于(>=);
-le:小于等于(<=);
-eq:等于(==);
-ne:不等于(!=) 注意到处都是空格
可以使用 &&(并且);||(或者) 结合多个条件
if [ $a -gt 5 ] && [ $a -lt 10 ]; then
解释:如果$a大于5并且$a小于10,则输出...
if [ $b -gt 5 ] || [ $b -lt 3 ]; then
解释:如果$b大于5或者$b小于3,则输出...
if判断文件、目录属性
[ -f file ]判断是否是普通文件且存在
[ -d file ] 判断是否是目录且存在
[ -e file ] 判断文件或目录是否存在
[ -r file ] 判断文件是否可读
[ -w file ] 判断文件是否可写
[ -x file ] 判断文件是否可执行
f="/usr/xjw"
[ -f $f ] && rm-rf $f
等同于
if [ -f $f ]
then
rm -rf $f
[ -f $f ] || touch $f
等同于
if [ ! -f $f ]
then
touch $f
shell中的case判断
if判断的一些特殊用法
if [ -z "$a" ] 这个表示当变量a的值为空时会怎么样
if [ -n "$a" ] 表示当变量a的值不为空
if grep -q '123' 1.txt; then 表示如果1.txt中含有'123'的行时会怎么样
if [ ! -e file ]; then 表示文件不存在时会怎么样
if (($a<1)); then …等同于 if [ $a -lt 1 ]; then…
[ ] 中不能使用<,>,==,!=,>=,<=这样的符号
shell中的case判断
格式:
case 变量名 in
value1)
command
;;
value2)
command
;;
*)除此自外
commond
;;
esac
在case程序中,可以在条件中使用|,表示或的意思, 比如
2|3)
command
;;
shell脚本案例
#!/bin/bash
read -p "Please input a number: " n
if [ -z "$n" ]
then
echo "Please input a number."
exit 1
fi
n1=`echo $n|sed 's/[0-9]//g'`
if [ -n "$n1" ]
then
echo "Please input a number."
exit 1
fi
if [ $n -lt 60 ] && [ $n -ge 0 ]
then
tag=1
elif [ $n -ge 60 ] && [ $n -lt 80 ]
then
tag=2
elif [ $n -ge 80 ] && [ $n -lt 90 ]
then
tag=3
elif [ $n -ge 90 ] && [ $n -le 100 ]
then
tag=4
else
tag=0
fi
for循环
语法格式for 变量名 in 条件; do …; done
案例1求1-100的和
#!/bin/bash
sum=0
for i in `seq 1 100`
do
sum=$[$sum+$i]
echo $i
done
echo $sum
for循环案例2
文件列表循环把etc下每个文件ls列一下
#!/bin/bash
cd /etc/
for a in `ls /etc/`
do
if [ -d $a ]
then
ls -d $a
fi
done
while循环
语法格式while 条件; do … ; done
案例1
#!/bin/bash
while :
do
load=`w|head -1|awk -F 'load average: ' '{print $2}'|cut -d. -f1`
if [ $load -gt 10 ]
then
top|mail -s "load is high: $load" asldkfls@11.com
fi
sleep 30
done
注:(while:死循环的意思)跟while true一样
案例2
#!/bin/bash
while :
do
read -p "Please input a number: " n
if [ -z "$n" ]
then
echo "you need input sth."
continue
fi
n1=`echo $n|sed 's/[0-9]//g'`
if [ -n "$n1" ]
then
echo "you just only input numbers."
continue
fi
break
done
echo $n
continue:结束本次循环,如果没达到目的会直接忽略continue下边的代码,直接进行下一次循环
continue是跳出当前条件循环,继续下一轮条件循环
break:跳出来,退出这个循环
break是立马跳出循环;continue是跳出当前条件循环,继续下一轮条件循环;
exit是直接退出整个脚本
如果写脚本的时候输入汉字发现是乱码,那证明系统变量语言不对LANG
用echo $LANG或者env|grep LANG看一下变量值如果是LANG=en就把他改成LANG=zh_CN.UTF-8
break跳出循环
#!/bin/bash
for i in `seq 1 5`
do
echo $i//输出i的值
if [ $i == 3 ]//当i等于三的时候跳循环就是跳出for循环
then
break
fi
echo $i
done
echo aaaaaaa
continue结束本次循环
忽略continue之下的代码直接进行下一次循环
#!/bin/bash
for i in `seq 1 5`
do
echo $i
if [ $i == 3 ]
then
continue
fi
echo $i
done
echo $i
exit直接退出脚本
#!/bin/bash
for i in `seq 1 5`
do
echo $i
if [ $i == 3 ]
then
exit
fi
echo $i
done
echo aaaaaaa
shell脚本中的函数
函数就是把一段代码整理到了一个小单元中并给这个小单元起一个名字当用到这段代码时直接调用这个小单元的名字即可。
格式: function f_name() {
command
}
解释:function后边跟函数的名字,或者可以不写function,直接写函数的名字:函数名()
函数必须要放在最前面
示例1 (打印一个参数
#!/bin/bash
input() {
echo $1 $2 $# $0
}
input 1 a b
示例2
#!/bin/bash
sum() {
s=$[$1+$2]
echo $s
}
sum 1 2
解释:首先定义一个函数sum,函数里边定义一个变量s,变量的内容是第一个参数与第二个参数相加,然后输出函数的结果,最下边sum是用来引用它,sum 1 2意思就是说1+2
示例3 输入网卡的名字显示网卡的IP0
#!/bin/bash
ip() {
ifconfig |grep -A1 "$1 " |tail -1 |awk '{print $2}'|awk -F':' '{print $2}'
}
read -p "Please input the eth name: " e
myip=`ip $e`
echo "$e address is $myip"
事例4:
#!/bin/bash
read -p "please inpute the ent name:" n
ip()
ifconfig |grep -A1 "$1: " | awk '/inet/ {print $2}'
}
ip $n
shell中的数组1
定义数组
格式:
a=(1 2 3 4 5); echo ${a[@]}
[root@localhost ~]# a=(1 2 3 4 5); echo ${a[@]} 1 2 3 4 5
a=(1 2 3 4 5); echo ${a[*]}
[root@localhost ~]# a=(1 2 3 4 5); echo ${a[*]} 1 2 3 4 5
查看指定某一元素的值:更改[ ]里边的值就可以
数组特性从零开始,0代表第一个,1代表第二个,依次往后排列
[root@localhost ~]# a=(1 2 3 4 5); echo ${a[0]} 1 [root@localhost ~]# a=(1 2 3 4 5); echo ${a[1]} 2 [root@localhost ~]# a=(1 2 3 4 5); echo ${a[2]} 3
echo ${#a[@]} 获取数组的元素个数
[root@localhost ~]# echo ${#a[@]} 5
数组赋值
a[1]=100; echo ${a[@]}如果下标或者说赋值的元素存在那么就会被替换或者说覆盖掉之前的
[root@localhost ~]# echo ${a[@]} //数组内容为1 2 3 4 5 a 1 2 3 4 5 a [root@localhost ~]# a[1]=100 //从新给元素赋值100 [root@localhost ~]# echo ${a[@]} //因为存在就会被替换掉,变成1 100 3 4 5 a 1 100 3 4 5 a [root@localhost ~]#
a[5]=a; echo ${a[@]} 如果下标不存在则会自动添加一个元素
[root@localhost ~]# a=(1 2 3 4 5) //a赋值为1 2 3 4 5 [root@localhost ~]# echo ${a[@]} //打印a数组 1 2 3 4 5 [root@localhost ~]# a[5]=a //给第五个元素赋值为a [root@localhost ~]# echo ${a[@]} //因为第五个元素不存在所以会增加一个 1 2 3 4 5 a
数组的删除
unset a 删除整个数组,其实跟取消赋值类似
[root@localhost ~]# echo ${a[@]} 1 100 4 5 a [root@localhost ~]# unset a [root@localhost ~]# echo ${a[@]} [root@localhost ~]#
unset a[2] 删除指定的下标元素
[root@localhost ~]# echo ${a[@]} 1 100 3 4 5 a [root@localhost ~]# unset a[2] [root@localhost ~]# echo ${a[@]} 1 100 4 5 a
数组分片
以 : 分割,第一个冒号后边表示你要从第几个元素开始截取,第二个冒号后边表示你要截取几个
a=(`seq 1 10`)
echo ${a[@]:0:3} 从第三个元素开始截取4个
[root@localhost ~]# a=(`seq 1 10`) [root@localhost ~]# echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 [root@localhost ~]# echo ${a[@]:3:4} 4 5 6 7
echo ${a[@]:1:4} 从第二个元素开始截取4个
[root@localhost ~]# echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 [root@localhost ~]# echo ${a[@]:1:4} 2 3 4 5
echo ${a[@]:0-3:2} 从倒数第3个元素开始截取2个
[root@localhost ~]# echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 [root@localhost ~]# echo ${a[@]:0-3:2} 8 9
echo ${a[@]:0-4:4} 从倒数第4个元素开始截取4个
[root@localhost ~]# echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 [root@localhost ~]# echo ${a[@]:0-4:4} 7 8 9 10
数组替换
用/分割,第一个/后边表示被替换的元素,第二个/后边表示要替换的元素
比如把数组中的3替换成100
echo ${a[@]/3/100}输出时替换
[root@localhost ~]# echo ${a[@]} 1 2 3 4 5 6 7 8 9 10 [root@localhost ~]# echo ${a[@]/3/100} 1 2 100 4 5 6 7 8 9 10
a=(${a[@]/3/100})永久替换,其实就是赋值
[root@localhost ~]# a=(${a[@]/3/100}) [root@localhost ~]# echo ${a[@]} 1 2 100 4 5 6 7 8 9 10
shell项目-告警系统
需求使用shell定制各种个性化告警工具但需要统一化管理、规范化管理。
思路指定一个脚本包包含主程序、子程序、配置文件、邮件引擎、输出日志等。
主程序作为整个脚本的入口是整个系统的命脉。
配置文件是一个控制中心用它来开关各个子程序指定各个相关联的日志文件。
子程序这个才是真正的监控脚本用来监控各个指标。
邮件引擎是由一个python程序来实现它可以定义发邮件的服务器、发邮件人以及发件人密码
输出日志整个监控系统要有日志输出。
要求:我们的机器角色多种多样但是所有机器上都要部署同样的监控系统也就说所有机器不管什么角色整个程序框架都是一致的不同的地方在于根据不同的角色定制不同的配置文件。
bin下是主程序
conf下是配置文件
shares下是各个监控脚本
mail下是邮件引擎
log下是日志。
shell项目-告警系统main.sh
main.sh内容
#!/bin/bash
#Written by aming.
# 是否发送邮件的开关
export send=1
# 过滤ip地址
export addr=`/sbin/ifconfig |grep -A1 "ens33: "|awk '/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 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
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.20.3.13
db_port=3315
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=1
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项目-告警系统load.sh
load.sh内容
#! /bin/bash
##Writen by aming##
load=`uptime |awk -F 'average:' '{print $2}'|cut -d',' -f1|sed 's/ //g' |cut -d. -f1`
if [ $load -gt 10 ] && [ $send -eq "1" ]
then
echo "$addr `date +%T` load 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"
shell项目-告警系统502.sh
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
fi
echo "`date +%T` 502 $c_502"
shell项目-告警系统disk.sh
disk.sh内容
#! /bin/bash
##Writen by aming##
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" ]
then
echo "$addr `date +%T` disk useage is $r" >>../log/disk.tmp
fi
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
注释:'[ %]+'以空格或者百分号为分隔符,+表示一个或者多个,结合起来就是以一个或多个空格或者百分号为分隔符(类似:'[:#]+'以冒号或者警号)
shell项目-告警系统mail.sh
mail.sh内容 //其中mail.py内容到这里下载https://coding.net/u/aminglinux/p/aminglinux-book/git/blob/master/D22Z/mail.py
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.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.py $1 "trouble continue 10 min $2" "$3"
echo "0" > /tmp/$log.txt
fi
fi
shell项目-分发系统-expect讲解
yum install -y expect
自动远程登录
#! /usr/bin/expect
set host "192.168.133.132"
set passwd "123456"
spawn ssh root@$host
expect {
"yes/no" { send "yes\r"; exp_continue}
"assword:" { send "$passwd\r" }
}
interact
shell项目-分发系统-expect讲解
自动远程登录后执行命令并退出
#!/usr/bin/expect
set user "root"
set passwd "123456"
spawn ssh $user@192.168.133.132
expect {
"yes/no" { send "yes\r"; exp_continue}
"password:" { send "$passwd\r" }
}
expect "]*"
send "touch /tmp/12.txt\r"
expect "]*"
send "echo 1212 > /tmp/12.txt\r"
expect "]*"
send "exit\r"
shell项目-分发系统-expect讲解
传递参数
#!/usr/bin/expect
set user [lindex $argv 0]
set host [lindex $argv 1]
set passwd "123456"
set cm [lindex $argv 2]
spawn ssh $user@$host
expect {
"yes/no" { send "yes\r"}
"password:" { send "$passwd\r" }
}
expect "]*"
send "$cm\r"
expect "]*"
send "exit\r"
shell项目-分发系统-expect讲解
自动同步文件
#!/usr/bin/expect
set passwd "123456"
spawn rsync -av root@192.168.133.132:/tmp/12.txt /tmp/
expect {
"yes/no" { send "yes\r"}
"password:" { send "$passwd\r" }
}
expect eof
shell项目-分发系统-expect讲解
指定host和要同步的文件
#!/usr/bin/expect
set passwd "123456"
set host [lindex $argv 0]
set file [lindex $argv 1]
spawn rsync -av $file root@$host:$file
expect {
"yes/no" { send "yes\r"}
"password:" { send "$passwd\r" }
}
expect eof
shell项目-分发系统-构建文件分发系统
需求背景对于大公司而言肯定时不时会有网站或者配置文件更新而且使用的机器肯定也是好多台少则几台多则几十甚至上百台。所以自动同步文件是至关重要的。
实现思路首先要有一台模板机器把要分发的文件准备好然后只要使用expect脚本批量把需要同步的文件分发到目标机器即可。
核心命令rsync -av --files-from=list.txt / root@host:/
shell项目-分发系统-构建文件分发系统
文件分发系统的实现
rsync.expect 内容
#!/usr/bin/expect
set passwd "123456"
set host [lindex $argv 0]
set file [lindex $argv 1]
spawn rsync -av --files-from=$file / root@$host:/
expect {
"yes/no" { send "yes\r"}
"password:" { send "$passwd\r" }
}
expect eof
ip.list内容
192.168.133.132
192.168.133.133
......
shell项目-分发系统-构建文件分发系统
rsync.sh 内容
#!/bin/bash
for ip in `cat ip.list`
do
echo $ip
./rsync.expect $ip list.txt
done
shell项目-分发系统-命令批量执行
exe.expect 内容
#!/usr/bin/expect
set host [lindex $argv 0]
set passwd "123456"
set cm [lindex $argv 1]
spawn ssh root@$host
expect {
"yes/no" { send "yes\r"}
"password:" { send "$passwd\r" }
}
expect "]*"
send "$cm\r"
expect "]*"
send "exit\r"
shell项目-分发系统-命令批量执行
exe.sh 内容
#!/bin/bash
for ip in `cat ip.list`
do
echo $ip
./exe.expect $ip "w;free -m;ls /tmp"
done