Shell脚本

shell脚本

一、有什么作用

简化操作步骤,提高效率,减少人为干预,减少系统故障

二、可以做什么

  1. 自动化的完成基础配置

    -系统初始化操作
    -系统更新
    -内核调整
    -网络
    -时区
    -ssh优化
    
  2. 自动化安装程序

    -自动化安装LNMP、LAMP、mysql、nginx、Redis
    
  3. 自动化调整配置文件

  4. 自动化部署业务

  5. 定期备份、恢复程序

    mysql全备+增量+binlog+crond+shell脚本
    
  6. 自动化信息采集(zabbix+shell)

    硬件、系统、服务、网络等等
    
  7. 自动化日志收集(elk)

  8. 自动化扩容或者缩容(zabbix+shell)

三、shell技能

1、shell的基础特性

1)命令和文件路径补全

安装bash-completion,然后重启生效

2)历史命令记忆功能

#执行history
[root@practice ~]# history 
    1  ifconfig 
    2  vmware-install.pl
    3  ls /dev/cdrom 
    4  ls
    5  ls /root/
    6  ls /root/Desktop/
    7  ls -a /root/Desktop/

3)别名功能

# alias 列出当前系统设置哪些别名
[root@practice ~]# alias 
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
# alias 别名="命令" 设置别名;
# 注意:命令行设置只能临时生效,关闭终端或者重启就不生效了
[root@practice ~]# alias ab="ls /root" 
[root@practice ~]# ab --->执行ab跟执行ls /root结果一样
anaconda-ks.cfg  creat_user.sh~  Documents             Music     Templates  Videos
a.txt            Desktop         Downloads             Pictures  test.gz    vmware-tools-distrib
creat_user.sh    docker          initial-setup-ks.cfg  Public    test.txt
[root@practice ~]# alias  
alias ab='ls /root'  <--- 查看设置,
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
# unalias 别名 临时取消别名
[root@practice ~]# unalias ab -->取消别名
[root@practice ~]# ab -->再次执行就会报错了
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path

4)输入输出重定向

  1. 重定向输入 >

  2. 追加重定向 >>

  3. 错误输入2> 错误追加重定向2>> &>文件 错误和正确都输入到文件中

  4. cat > 文件名<<EOF

    # cat > 文件之前有内容会被覆盖;cat >>追加
    [root@practice ~]# cat >test.txt<<EOF
    > www   --> 键盘输入的内容
    > eer
    > rtrt
    > EOF
    [root@practice ~]# cat test.txt 
    www
    eer
    rtrt
    

    5)管道

# 将前者命令的标准输出作为后者命令的输入
[root@practice ~]# echo 123456|passwd --stdin root
Changing password for user root.
passwd: all authentication tokens updated successfully.

6)命令排序

a、 ;号  没有逻辑关系,无论分号前面的命令是否执行成功,分号后的都执行
b、 && 前面命令执行成功,才执行后者
c、 || 前面命令执行不成功,才执行后者

7)通配符

*  匹配任意字符
? 匹配任意一个字符
[] 匹配括号中任意一个字符-->[0-9][a-z][A-Z][a-Z]
{} 集合 touch file{1..9}
\  转义字符

8)echo输出颜色

# echo -e "\033[32;1m 内容\033[0m"
[root@practice ~]# echo -e "\033[32;1m 内容\033[0m"
 内容
[root@practice ~]# echo -e "\033[33;1m 内容\033[0m" 
 内容
[root@practice ~]# echo -e "\033[34;1m 内容\033[0m" 
 内容

2、变量

1)变量类型

a、自定义变量
# 用ping测试主机开机状态,接收用户输入,脚本仍需要要对用户输入IP进行判断,后续再完善---->另一版本请看(4、循环语句-->for循环)
[root@practice shell]# cat ping.sh 
#!/bin/bash
read -p "请输入要测试的IP地址:"  IP
ping -c2 $IP &>/dev/null && echo -e "$IP is \033[32;1mUP\033[0m"|| echo -e "$IP is \033[31;1mDOWN\033[0m"
# 自定义变量只在当前shell中有效
- 定义变量:变量名=值
- 查看变量:echo $变量名 或者 echo ${变量名}
- 引用变量:$变量名 或者 ${变量名}
- 取消变量:unset 变量名
- 查看所有变量:set
b、系统环境变量
# 作用域是全局
[root@practice shell]# echo $HOME
/root
[root@practice shell]# echo $USER
root
- 定义变量:export 变量名=值 -->全局都可以调用,命令行只是临时定义,要永久生效需要把这一行写到/etc/profile中,然后source /etc/profile 或者重启生效
- 引用变量:$变量名 或者 ${变量名}
c、预先定义变量
$0:脚本名  
$*:所有的参数
$@:所有的参数
$#:参数的个数
$$:当前进程的pid
$?:上一个命令返回值为0,表示成功
d、位置参数变量
脚本传参:$1 $2 $3 .. ${10},十个位置参数

2)变量运算

a、数值运算
# expr做整数运算运算符两侧一定要有空格
[root@practice ~]# a=3
[root@practice ~]# b=5
[root@practice ~]# expr $a + $b
8
[root@practice ~]# expr $a+$b  -->不加空格结果不对
3+5
[root@practice ~]# expr $b % $a
2
[root@practice ~]# expr $b \* $a -->特殊字符要转义一下
15
[root@practice ~]# expr $b - $a 
2
# echo $((变量1+变量2)),运算符两侧不需要有空格
[root@practice ~]# echo $((a+b))
8
[root@practice ~]# echo $((a-b))
-2
[root@practice ~]# echo $((a*b))
15
[root@practice ~]# echo $((a/b))
0
[root@practice ~]# echo $((a%b))
3
[root@practice ~]# echo $(((a+b)*b)) -->括号优先
40
# echo $[变量1+变量2],运算符两侧不需要有空格
[root@practice ~]# echo $[a+b]
8
[root@practice ~]# echo $[a-b]
-2
[root@practice ~]# echo $[a*b]
15
[root@practice ~]# echo $[a/b]
0
[root@practice ~]# echo $[a%b]
3
[root@practice ~]# echo $[(a+b)*b]
40
# let 需要另一个c变量来接收a和b的运算结果
[root@practice ~]# let c=a+b
[root@practice ~]# echo $c
8
# bc计算器可以用来做小数运算,保留小数需要先定义scale=n,保留n位小数
[root@practice ~]# echo 1+2|bc
3
[root@practice ~]# echo "scale=2;4/2"|bc
2.00
[root@practice ~]# echo "scale=2;2/4"|bc
.50
b、变量内容替换
# "#"是从前往后切
url=www.baidu.com
[root@practice ~]# echo ${#url} -->获取变量的长度
13
[root@practice ~]# echo ${url#*.} -->一个#号是最短匹配,从前匹配到.一起切掉
baidu.com
[root@practice ~]# echo ${url##*.} -->两个#号是贪婪匹配,切到最后一个.
com
# "%"是从后往前切
url=www.baidu.com
[root@practice ~]# echo ${url%.*} -->从后往前切到第一个.
www.baidu
[root@practice ~]# echo ${url%c*} -->从后往前切到第一个c
www.baidu.
[root@practice ~]# echo ${url%d*} -->从后往前切到第一个d
www.bai
[root@practice ~]# echo ${url%%.*} -->两个%号是最长匹配
www
# 按索引截取需要的部分,索引从0开始 ${变量:开始索引:从开始索引往后截取几位}
url=www.baidu.com
[root@practice ~]# echo ${url:5} -->截取第五个下标开始到最后
aidu.com
[root@practice ~]# echo ${url:0:5} -->截取0到0往后的5位
www.b
[root@practice ~]# echo ${url::5} -->0可以省略
www.b
[root@practice ~]# echo ${url:5:7} -->截取第五位开始,往后的七位
aidu.co
[root@practice ~]# echo ${url:5:3} -->截取第五位开始,往后的三位
aid

# 字符串的替换
url=www.baidu.baidu.com
[root@practice ~]# echo ${url/baidu/sina} -->"/"最短替换
www.sina.baidu.com
[root@practice ~]# echo ${url//baidu/sina} -->"//"最长替换
www.sina.sina.com
# 变量默认值,${变量名:-默认值},变量没有定义会使用默认值;变量有值则不会给变量赋默认值
[root@practice ~]# echo ${url:-sina} -->url有值,就输出自己的值   
www.baidu.baidu.com
[root@practice ~]# echo ${ur:-sina} -->ur没有定义,就给变量赋默认值
sina
c、自增、自减
i=1 let x=i++ -->先赋值再运算-->x=1
j=1 let y=++j -->先运算再赋值-->y=2
# 打印0-5
#!/bin/bash
i=0
while [ $i -le 5 ];
do
echo $i
let i++ -->自增1
done

3、条件判断

1)条件测试

# 整数数值比较
-eq -->等于
-gt -->大于
-ge -->大于等于
-lt -->小于
-le -->小于等于
-ne -->不等于
# 多个整数比较
[ 2 -eq 2 -a 3 ge 1 ] --> -a 是and并且的意思
[ 2 -eq 2 -a 3 ge 1 ] --> -o 是or或者的意思
[[ 2 -eq 2 && 3 ge 1 ]] --> 两个中括号用&&,并且
[[ 2 -eq 2 || 3 ge 1 ]] --> 两个中括号用||,或者
# 字符串比较
== 等于
!= 不等于

-e dir|file -->目录或者文件是否存在,存在为真
-d dir -->目录是否存在
-f file -->文件是否存在
-r file -->文件是否有读权限
-x file -->文件是否有执行权限
-w file -->文件是否有写权限
# 格式1--> test 条件表达式
[root@practice ~]# test -d /testdir || mkdir /testdir -->判断/testdir目录是否存在,不存在则执行后边的命令;存在则不做处理
# 格式2-->[ 条件表达式 ]
[root@practice ~]# which [
/usr/bin/[ -->"["是一个命令,所以左右都要有空格的
# 格式3-->[[条件表达式]]
a、案例1-->数据库备份
# Msql某一个库的备份
[root@practice shell]# vim mysql_dump.sh
#!/bin/bash
# 以绝对路径执行mysqldump命令
MysqlDump=/usr/bin/mysqldump
User=用户名
Pass=密码
Data=备份指定的库
BackDir=/backup  # 保存的路径
Date=$(date +%F)
BackPath=$BackDir/$Date/mysql_db.sql
# 先探测一下该目录是否存在
test -d $BackPath || mkdir $BackPath
if [ $? -eq 0 ];then
    echo "==========Backup is Starting==========="
    # 备份命令
    $MysqlDump -u$User -p$Pass -D $Data >$BackPath
    if [ $? -eq 0 ];then
    echo "==========MysqlDump is done==========="
    fi
fi
b、案例2-->磁盘使用率
#根分区磁盘的使用率超过80%就写入到指定文件中或者发邮件
#!/bin/bash
DiskFree=$(df -h|grep /$|awk '{print $(NF-1)}'|awk -F '%' '{print $1}')
if [ $DiskFree -ge 80 ];then
echo "The usage of / is ${DiskFree}%" > /mnt/diskfree.txt
fi
c、案例3-->内存使用率
#!/bin/bash
Mem_total=$(free -m|grep "^M"|awk '{print $2}')
Mem_used=$(free -m|grep "^M"|awk '{print $3}')
Mem_Percentage=$[($Mem_used*100)/Mem_total]
if [ $Mem_Percentage -ge 60 ];then
echo "Memory is full"
else
echo "Memory is ok"
fi
d、案例4-->cpu每分钟负载
#!/bin/bash
# 每分钟的cpu负载
Cpu_Load_Per=$(w|awk 'NR==1'|awk -F ':' '{print $NF}'|awk -F '.' '{print $1}')
if [ $Cpu_Load_Per -ge 1 ];then
echo -e "CPU Load is \033[31;1merr\033[0m $Cpu_Load_Per "
else
echo -e "CPU Load is \033[32;1mok\033[0m  $Cpu_Load_Per"
fi
# 可以用ab进行测试
ab -c 10000 -n 100 http://localhost
-c-->10000次请求
-n-->每次请求并发100
e、综合案例
1、打印当前系统版本、内核、虚拟化平台、主机名、内网ip、外网ip
2、打印当前CPU负载,1分钟,5分钟,15分钟
3、打印当前磁盘所有分区使用状态
4、打印当前内存总大小,使用了多少
5、检测该主机是否能上网

4、循环语句

# 影响循环的内置命令
exit 退出程序
continue 结束本次循环,继续下一次循环
break退出当前循环

1)for循环

# for循环打印1-5,第一种方式
#!/bin/bash
for i in {1..5}
do
 echo $i
done
## 第二种方式
#!/bin/bash
for ((i=1;i<=5;i++)) --->这种写法更接近于python或者java
do
 echo $i
done
# 测试局域网内主机的开机状态
#!/bin/bash
IP=10.0.0
for i in {1..10}
do
  ping -c1 $IP.$i &>/dev/null
  if [ $? -eq 0 ];then
    echo -e "$IP.$i is \033[32;1mUP\033[0m"
  else
    echo -e "$IP.$i is \033[31;1mDOWN\033[0m"
  fi
done
echo "========The test of Network is finished========="
## 可以使用 time +脚本 来测试执行的时间
[root@practice shell]# time ./ping_enhance.sh 
10.0.0.1 is UP
10.0.0.2 is UP
10.0.0.3 is DOWN
10.0.0.4 is DOWN
10.0.0.5 is DOWN
10.0.0.6 is DOWN
10.0.0.7 is DOWN
10.0.0.8 is DOWN
10.0.0.9 is DOWN
10.0.0.10 is DOWN
========The test of Network is finished=========

real    0m24.101s --->执行脚本需要的时间24秒
user    0m0.019s
sys     0m0.033s
# 使用并发控制来测试主机,提升效率
#!/bin/bash
IP=10.0.0
for i in {1..10}
do
{   #---->1、要执行的语句用{}大括号包起来
  ping -c1 $IP.$i &>/dev/null
  if [ $? -eq 0 ];then
    echo -e "$IP.$i is \033[32;1mUP\033[0m"
  else
    echo -e "$IP.$i is \033[31;1mDOWN\033[0m"
  fi
}&  #----->2、另一半大括号,并加上&符号,后台执行
done
wait #----->3、加上关键字wait
echo "========The test of Network is finished========="
## 再来测试脚本执行的时间
[root@practice shell]# time ./ping_enhance-并发.sh   
10.0.0.2 is UP
10.0.0.1 is UP
10.0.0.3 is DOWN
10.0.0.6 is DOWN
10.0.0.7 is DOWN
10.0.0.4 is DOWN
10.0.0.8 is DOWN
10.0.0.10 is DOWN
10.0.0.5 is DOWN
10.0.0.9 is DOWN
========The test of Network is finished=========

real    0m3.036s --->执行脚本的时间明显快很多,只需要3秒
user    0m0.011s
sys     0m0.029s

2)while循环

# while循环打印1-5:
#!/bin/bash
i=1
while [ $i -le 5 ];
do
 echo $i
 let i++
done
## 另一种写法
#!/bin/bash
i=1
while (($i<=5));
do
 echo $i
 let i++
done
# 计算1-50之间的偶数和
#!/bin/bash
sum=0
for i in {0..50}
do
  if [  $(($i%2)) -eq 0 ];then
  let sum=$sum+$i
  fi
done
echo $sum
## 另一种方法:
i=0
sum=0
while [ $i -le 50 ];
do
 let sum+=i
 let i+=2
done
echo $sum
# 需求:
1、30秒同步一次系统时间,时间服务器为ntp1.aliyun.com-->同步时间的两个命令ntpdate 时间服务器 或者 rdate -s 时间服务器
2、如果同步失败,每次都报警
3、如果成功,则100次发一次邮件
# 脚本实现
#!/bin/bash
NTP=ntp1.aliyun.com
COUNT=0
while :
do
  ntpdate $NTP
  if [ $? -eq 0 ];then
    let COUNT++
    if [ $[NUM%3] -eq 0 ];then
      echo "systen date success"|mail -s "check system date" root@localhost && COUNT=0 --->发邮件之后计数重置为0
    fi
  else
    echo "system date is err"|mail -s "check system date" root@localhost
  fi
  sleep 2 --->间隔两秒同步一次,时间可以自行调整
done

#!/bin/bash
# 随机生成139开头的1000个电话号码,写到/mnt/phone.txt
for ((i=1;i<=1000;i++))
do
  r1=$[RANDOM%10]
  r2=$[RANDOM%10]
  r3=$[RANDOM%10]
  r4=$[RANDOM%10]
  r5=$[RANDOM%10]
  r6=$[RANDOM%10]
  r7=$[RANDOM%10]
  r8=$[RANDOM%10]
echo "139$r1$r2$r3$r4$r5$r6$r7$r8" >> /mnt/phone.txt
done
# 循环抽出五位幸运号码
j=1
while [ $j -le 5 ];
do
luck_num=$[RANDOM%1000+1]
luck_phone=`awk "NR==$luck_num {print}" /mnt/phone.txt`
echo $luck_phone >> /mnt/luck_phone.txt
echo -e "第$j个幸运号码是:139****${luck_phone:7}"
# 删除获奖的号码
sed -i '/$luck_phone/d' /mnt/phone.txt
let j++
done
# 需求:批量创建用户,密码随机产生
#!/bin/bash
echo user0{1..5}:itcast$[RANDOM%9000+1000]#@~|tr ' ' '\n' > /mnt/user_pass.txt -->创建一个用户和随机密码文件
for i in `cat /mnt/user_pass.txt`
do
 user=`echo $i|awk -F ':' '{print $1}'`
 pass=`echo $i|awk -F ':' '{print $2}'`
 useradd $user
 echo $pass | passwd --stdin $user
done

3)嵌套循环

5、流程控制语句

# case语法结构
case 变量 in
mode1|M)-->两个满足其中一个
   命令1;;
mode2)
   命令2;;
   ...
default)--->不满足以上命令,默认执行的
   命令;;
 esac
# 远程连接跳板机
#!/bin/bash
web1=10.0.0.1
web2=10.0.0.2
Mysql=10.0.0.31
Nfs=10.0.0.41
menu() {  -->定义一个菜单函数
cat <<EOF
 1. web1=10.0.0.1
 2. web2=10.0.0.2
 3. Mysql=10.0.0.31
 4. Nfs=10.0.0.41
 5. 菜单
EOF
}
menu -->调用函数,显示菜单
trap "" INT TSTP HUP -->不让中断脚本
while : -->死循环
do
read -p "请输入您要连接的服务器名称[1|web1]:" num
case $num in 
1)
 ssh root@$web1;;
2)
 ssh root@$web2;;
3)
 ssh root@$Mysql;;
4)
 ssh root@$Nfs;;
5)
 menu;;
woshiyunwei) -->自己定义的,可以跳出循环
 exit;;
*)
 read -p "请输入您要连接的服务器名称[1|web1]:" num
esac
done

6、函数(function)

# 函数的三种定义方式
#!/bin/bash
test1(){
echo "第一种定义方式"
}

function test2(){
 echo "第二种定义方式"
}

function test3 { -->第三种方式定义函数名和大括号之间一定要有空格
 echo "第三种定义方式"
}
# 函数调用直接写函数名即可
test1
test2
test3

7、数组(array)

# 分类
- 普通数组:只能以整数作为数组索引
- 关联数组:可以使用字符串作为数组索引
# 结构
arrayname[string]=value
数组名[元素]=元素的值
# 定义(赋值)
第一种
[root@practice shell]# array1[0]=shell
[root@practice shell]# array1[1]=shell1 
第二种
[root@practice shell]# array2=([0]=shell2 [1]=shell3 [4]=shell4)
第三种 -->比较常用的
[root@practice shell]# array3=(mysql1 mysql2 mysql3)
[root@practice shell]# echo ${array3[*]}
mysql1 mysql2 mysql3
[root@practice shell]# echo ${!array3[*]}
0 1 2


[root@practice shell]# echo $array1
shell -->默认是只打印索引为0的值
[root@practice shell]# echo ${array1[*]}
shell shell1 -->打印所有值
[root@practice shell]# echo ${array1[@]}
shell shell1 -->打印所有值
[root@practice shell]# echo ${!array1[@]}
0 1  -->打印数组的索引
# 取消数组
unset 数组名

#!/bin/bash
IP=(10.0.0.1  # -->定义数组
10.0.0.2
10.0.0.3)
for i in ${IP[*]}
do
ping -c2 $i &>/dev/null
if [ $? -eq 0 ];then
echo "ping $i is ok"
else
echo "ping $i in err"
fi
done
# 目的:打印shell的种类和数量
#!/bin/bash
declare -A array #-->定义一个关联数组
while read line #-->逐行读入
do
FIN=`echo $line|awk -F: '{print $NF}'` #-->取每一行的最后一列
let array[$FIN]++
done</etc/passwd
for i in ${!array[*]} #-->获得每个元素的索引
do
 echo $i,${array[$i]} #-->根据索引获得值
done
# 利于awk数组来打印shell的种类的数量
[root@practice ~]# awk -F: '{array[$NF]++}END{for (i in array) print i,array[i]}' /etc/passwd     
/bin/sync 1
/bin/bash 9
/sbin/nologin 38
/sbin/halt 1
/sbin/shutdown 1

8、正则表达式

posted @ 2020-07-07 15:50  昭昭木木  阅读(129)  评论(0编辑  收藏  举报