shell编程
shell编程能做什么
1)安装操作系统(手动安全ISO) 自动化安装(kickstart cobbler)shell脚本
2)初始化操作系统 优化(SSH优化、关闭SElinux、优化防火墙、NTP时间同步、更改默认YUM、源字符集、安装常用的软件 lrzsz net-tools tree.. wget、隐藏版本信息、加大文件描述符、内核优化...)初始化写入脚本 自动化执行
3) 安装服务(Nginx PHP MySQL MariaDB NFS sersync REDIS keepalived docker....zabbix)使用shell脚本自动选择安装
4) 启动----停止(centos6.x: /etc/init.d/server start;centos7.x:systemctl start server) 系统默认的启动方式 shell脚本
5) 监控 zabbix、cacti、nagios、ELK、公司研发的监控平台;监控的值(shell脚本统计)
6) 日志统计 三剑客 日志切割(脚本+定时任务)
初识Shell
什么是shell
pwd ls 都是通过bash解释的、Shell是一个命令解释器,作用是解释执行用户输入的命令及程序
交互式模式就是shell等待你的输入,并且执行你提交的命令。这种模式被称作交互式是因为shell与用户进行交互。这种模式也是大多数用户非常熟悉的:登录、执行一些命令、签退。当你签退后,shell也终止了。shell也可以运行在另外一种模式:非交互式模式。在这种模式下,shell不与你进行交互,而是读取存放在文件中的命令,并且执行它们。当它读到文件的结尾,shell也就终止了。
什么是shell脚本
命令的大礼包 把可执行命令放到文件中 称为shell脚本(判断语句 循环语句 数组....)
编译语言: 把代码编程成二进制语言 机器可识别 一次编译 终身运行 速度快
脚本语言: 每次执行脚本 一行行的解释
shell的书写规范
- shell脚本存放统一的目录
- 脚本名字的结尾使用.sh
- 脚本的开头 必须有解释器 #!/bin/bash
- 脚本内有作者信息 脚本信息
- 每段代码块有注释(尽量使用英文)
- 标点符号 (语法尽量一次性书写完毕)
- 成对的符号 一次性书写完毕
第一个shell脚本
[root@test scripts]# vim hello.sh
#!/bin/bash
echo "Hello Word!"
[root@test scripts]#
#执行脚本的三种方法
#方法1 直接使用解释器执行
[root@test scripts]# bash hello.sh
Hello Word!
[root@test scripts]#
#方法2 全路径方式执行 需要执行权限
[root@test scripts]# chmod +x hello.sh
[root@test scripts]# ./hello.sh
Hello Word!
[root@test scripts]#
#方法3 source .执行脚本 子shell中的内容调用到父shell中执行
[root@test scripts]# source hello.sh
Hello Word!
[root@test scripts]# . hello.sh
Hello Word!
[root@test scripts]#
环境变量
什么是环境变量?
LANG='en_US.UTF-8'
name=test
右边一堆内容 使用一个名字来代替称为环境变量
如何查看变量环境变量 加$
echo $name
env 查看系统定义的环境变量
变量的分类
全局变量(环境变量) 针对系统所有用户生效
局部变量(普通变量) 针对当前登录用户生效
变量生存周期分类 两类
临时性 export 声明变量即可 或者 name=oldboy
永久性 修改配置文件 /etc/profile
#环境变量文件执行顺序
1. /etc/profile
2. .bash_profile
3. .bashrc
4. /etc/bashrc
#如果按照文件内容的生效顺序
1. /etc/profile
2. .bashrc
3. /etc/bashrc
4. .bash_profile
如何定义环境变量
要求以字母 数字 和下划线组合 尽量以字母和_开头 等号两端不允许有空格 名字 见名知其意
变量名字的定义的语法:
OLDBOY_AGE=18 系统用的变量都是大写
oldboy_age=18 全小写
oldboy_Age=18 小驼峰语法
Oldboy_Age=18 大驼峰语法
变量值得定义:
1.数字定义 连续的数字 test=188888
2.字符串定义 连续的字符串 name="test test test" 不知道加什么就加双引号
例子: CODE_DIR=/etc/sysconfig/network-scripts/
[root@test scripts]# name=opesn
[root@test scripts]# echo "$name"
opesn
[root@test scripts]# echo '$name'
$name
[root@test scripts]#
3.命令的定义
方法1 [root@test scripts]# tim=`date +%F`
方法2 [root@test scripts]# tim=$(date +%F)
特殊的位置变量
$0 代表了脚本的名称,如果使用全路径执行则脚本名称带全路径
$n 代表脚本的第n个参数 $0被脚本名称占用 参数从$1开始 $9 以后需要加{}
$# 代表脚本传参的总个数
$* 获取脚本的所有的参数 不加双引号和$@相同 加上双引号则把参数视为一个整体 $1$2$3(在循环体中)
$@ 获取脚本的所有的参数 不加双引号和$* 相同 加上双引号则把参数视为独立的(在循环体中)
$? 获取上一条命令的结果 0为成功 非0失败 0-255之间
$$ 获取脚本的PID
$! 上一个在后台运行的脚本的PID 调试使用
$_ 获取命令行或脚本的最后一个参数 相当于ESC .
变量传参的三种方法
方法1 直接传参
方法2 赋值传参
方法3 read传参
#方法1
[root@test scripts]# vim test.sh
#!/bin/bash
echo $1 $2
[root@test scripts]# bash test.sh 1 2
1 2
[root@test scripts]#
#方法2
[root@test scripts]# vim test.sh
#!/bin/bash
name1=$1
name2=$2
echo ${name1} ${name2}
[root@test scripts]# bash test.sh 1 2
1 2
[root@test scripts]#
#方法3
[root@test scripts]# bash test.sh
please input name : 1
please input name : 2
1
2
[root@test scripts]#
#使用read方式更改主机名和IP地址
[root@test scripts]# vim hostname_and_ip.sh
#!/bin/bash
eth=/etc/sysconfig/network-scripts/ifcfg-eth0
read -p "please input hostname: " name
hostnamectl set-hostname ${name}
read -p "please input IP: " ipadd
sed -i "/IPADDR/c IPADDR=${ipadd}" ${eth}
[root@test scripts]#
[root@test scripts]# bash hostname_and_ip.sh
please input hostname: opesn
please input IP: 10.0.1.250
[root@test scripts]#
变量的子串
#计算变量的长度
[root@test scripts]# echo $test|wc -L
10
[root@test scripts]# echo ${#test}
10
[root@test scripts]# expr length "$test"
10
[root@test scripts]# echo $test |awk '{print length}'
10
[root@test scripts]#
#截取变量中的字符
[root@test scripts]# echo ${test:2:2}
am
[root@test scripts]# echo ${test:2}
am opesn
[root@test scripts]# echo ${test:2:4}
am o
[root@test scripts]#
变量子串的删除和替换
#从前面往后面删除(##代表贪婪匹配)
[root@test scripts]# url=www.baidu.com
[root@test scripts]# echo ${url}
www.baidu.com
[root@test scripts]# echo ${url#*.}
baidu.com
[root@test scripts]# echo ${url#*.*.}
com
[root@test scripts]# echo ${url##*.}
com
[root@test scripts]#
#从后面往前面删除(%代表贪婪匹配)
[root@test scripts]# url=www.baidu.com
[root@test scripts]# echo ${url}
www.baidu.com
[root@test scripts]# echo ${url%.*}
www.baidu
[root@test scripts]# echo ${url%.*.*}
www
[root@test scripts]# echo ${url%%.*}
www
[root@test scripts]#
#替换(//代表贪婪匹配)
[root@test scripts]# url=www.baidu.com
[root@test scripts]# echo ${url}
www.baidu.com
[root@test scripts]# echo ${url/baidu/qq}
www.qq.com
[root@test scripts]# echo ${url/w/a}
aww.baidu.com
[root@test scripts]# echo ${url//w/a}
aaa.baidu.com
[root@test scripts]#
数值运算
#expr只能做整数运算
[root@test scripts]# expr 1 + 1
2
[root@test scripts]# expr 5 - 5
0
[root@test scripts]# expr 1 * 5
5
[root@test scripts]# expr 10 / 5
2
[root@test scripts]#
#$[]只能做整数运算
[root@test scripts]# echo $[10+10]
20
[root@test scripts]# echo $[10-10]
0
[root@test scripts]# echo $[10*10]
100
[root@test scripts]# echo $[10/10]
1
[root@test scripts]#
#$(())只能做整数运算,效率最高的运算
[root@test scripts]# echo $((5+5))
10
[root@test scripts]# echo $((5-5))
0
[root@test scripts]# echo $((5*5))
25
[root@test scripts]# echo $((5/5))
1
[root@test scripts]#
#let只能做整数运算
[root@test scripts]# let sum=1+1
[root@test scripts]# echo $sum
2
[root@test scripts]#
#bc整数、小数运算
[root@test scripts]# echo 10+10|bc
20
[root@test scripts]# echo 10-10|bc
0
[root@test scripts]# echo 10*10|bc
100
[root@test scripts]# echo 10/10|bc
1
[root@test scripts]#
#awk小数、整数运算
[root@test scripts]# awk 'BEGIN{print 10-5.5}'
4.5
[root@test scripts]# awk 'BEGIN{print 10+5.5}'
15.5
[root@test scripts]# awk 'BEGIN{print 10*5}'
50
[root@test scripts]# awk 'BEGIN{print 10/5}'
2
[root@test scripts]#
#python小数、整数运算
[root@test scripts]# python
>>> 10+5
15
>>> 10-2
8
>>> 5*5
25
>>> 5/5
1
>>>
#expr 判断传参是否为整数
[root@test ~]# vim expr.sh
#!/bin/bash
num1=$1
num2=$2
expr $1 + $2 &>/dev/null
[ $? -ne 0 ] && echo "请输入两个整数" && exit
echo "${num1}+${num2}=$[${num1}+${num2}]"
[root@test ~]# bash expr.sh 1 1.5
请输入两个整数
[root@test ~]# bash expr.sh 1 1
1+1=2
[root@test ~]#
条件表达式
[ -f file ]
-e 文件存在则为真
-f 是否存在并且为普通文件
-d 是否为目录
-r 是否可读
-w 是否可写
-x 是否可执行
[root@test ~]# [ -e /etc ] && echo "存在"||echo "不存在"
存在
[root@test ~]# [ -e /etcc ] && echo "存在"||echo "不存在"
不存在
[root@test ~]# [ -f /etc ] && echo "存在"||echo "不存在"
不存在
[root@test ~]# [ -f /etc/hosts ] && echo "存在"||echo "不存在"
存在
[root@test ~]#
数值表达式
[ 整数1 比较符 整数2 ]
-eq 等于
-ne 不等于
-gt 大于
-ge 大于等于
-lt 小于
-le 小于等于
[root@test ~]# [ 10 -eq 10 ] && echo "ok" ||echo "no"
ok
[root@test ~]# [ 10 -gt 10 ] && echo "ok" ||echo "no"
no
[root@test ~]# [ 10 -ge 10 ] && echo "ok" ||echo "no"
ok
[root@test ~]# [ 10 -lt 10 ] && echo "ok" ||echo "no"
no
[root@test ~]# [ 10 -le 10 ] && echo "ok" ||echo "no"
ok
[root@test ~]# [ 10 -ne 10 ] && echo "ok" ||echo "no"
no
[root@test ~]#
多整数比较
[ 整数1 比较符 整数2 -o 整数3 比较符 整数4 ]
[ 整数1 比较符 整数2 -a 整数3 比较符 整数4 ]
-o 或
-a 与
[root@test ~]# [ 10 -eq 10 -o 10 -ne 10 ]
[root@test ~]# echo $?
0
[root@test ~]# [ 10 -eq 10 -a 10 -ne 10 ]
[root@test ~]# echo $?
1
[root@test ~]#
字符串比较
#字符串比较必须加双引号
[root@test ~]# [ "$USER" = root ]
[root@test ~]# echo $?
0
[root@test ~]# [ "$USER" = "root" ]
[root@test ~]# echo $?
0
[root@test ~]# [ "$USER" = "opesn" ]
[root@test ~]# echo $?
1
[root@test ~]# [ ! "$USER" = "opesn" ]
[root@test ~]# echo $?
0
[root@test ~]#
-z 值为0,则为真
-n 值不为0,则为真
[root@test ~]# [ -n "" ] && echo 0 || echo 1
1
[root@test ~]# [ -n "s" ] && echo 0 || echo 1
0
[root@test ~]#
[root@test ~]# [ -z "s" ] && echo 0 || echo 1
1
[root@test ~]# [ -z "" ] && echo 0 || echo 1
0
正则对比
正则比对 [[]]
取反 ! 写在表达式的前面
[root@test ~]# [[ $USER =~ ^r ]]
[root@test ~]# echo $?
0
[root@test ~]# [[ $USER =~ ^n ]]
[root@test ~]# echo $?
1
[root@test ~]#
[root@test ~]# [[ ! $USER =~ ^n ]]
[root@test ~]# echo $?
0
[root@test ~]# [[ ! $USER =~ ^r ]]
[root@test ~]# echo $?
1
[root@test ~]#
if判断
#单分支(单分支:一个条件一个结果)
[root@test test]# vim if.sh
#!/bin/bash
if [ -f /etc/passwd ];then
echo "yes"
fi
#双分支(双分支:一个条件两个结果)
[root@test test]# vim if.sh
#!/bin/bash
if [ -f /etc/passwdd ];then
echo "passwdd file is yes"
else
echo "passwdd file is no"
fi
#多分支(多分支:多个条件多个结果)
[root@test test]# vim if.sh
#!/bin/bash
if [ -f /etc/passwdd ];then
echo "passwdd file is yes"
elif [ -f /etc/passwddd ];then
echo "passwddd file is yes"
elif [ -f /etc/passwd ];then
echo "passwd file is yes"
fi
case语句
case 变量 in
变量内容1)
命令组
;;
变量内容2)
命令组
;;
变量内容3)
命令组
;;
*)
echo "请输入正确的变量"
esac
case语句书写
[root@test ~]# vim jumpserver.sh
#!/bin/bash
clear
cat <<EOF
* * * * * * * * * * * * * * *
* 1.m01=10.0.1.61 *
* q.退出 *
* * * * * * * * * * * * * * *
EOF
trap "" INT HUP TSTP
while true
do
read -p "请输入你要连接的服务器:" sum
case $sum in
1)
clear
ssh 10.0.1.61
clear
cat <<EOF
* * * * * * * * * * * * * * *
* 1.m01=10.0.1.61 *
* q.退出 *
* * * * * * * * * * * * * * *
EOF
;;
q)
exit
;;
*)
cat <<EOF
* * * * * * * * * * * * * * *
* 1.m01=10.0.1.61 *
* q.退出 *
* * * * * * * * * * * * * * *
EOF
esac
done
for循环
for 变量名称 in 取值列表 (数字 字符串 `cat 文件` 命令)
do
命令
echo hehe
done
for循环书写
[root@test ~]# vim ping.sh
#!/bin/bash
. /etc/init.d/functions
for i in {60..65}
do
{
ping -c1 -w1 10.0.1.$i &>/dev/null
[ $? -eq 0 ] && action "ping 10.0.1.$i is ok" /bin/true
} &
done
wait
echo "在线取IP is ok"
while循环
while [ 条件 ] 为真则执行 为假退出
[root@test ~]# vim while_sum+.sh
#!/bin/bash
sum=0
i=1
while true
do
sum=$[${sum}+${i}]
[ $i -eq 100 ] && echo $sum && exit
((i++))
done
语句控制
exit #直接退出整个脚本
break #跳出循环体 执行循环体外的命令
continue #跳出当前的循环 继续下一次循环
exit
[root@test test]# cat exit.sh
#!/bin/sh
while true
do
echo hehe
exit
done
echo done.......
[root@test test]# bash exit.sh
hehe
[root@test test]#
break
[root@test test]# cat break.sh
#!/bin/sh
while true
do
echo hehe
break
done
echo done.......
[root@test test]# bash break.sh
hehe
done.......
[root@test test]#
continue
[root@test test]# cat continue.sh
#!/bin/sh
for i in {1..5}
do
[ $i -eq 4 ] && continue
echo $i
done
echo done.......
[root@test test]# bash continue.sh
1
2
3
5
done.......
[root@test test]#
函数
1) 完成特定功能的代码块
2) 代码模块化 便于复用和可读
3) 和变量类似 必须先定义在调用,如果不调用则函数不会执行
函数的定义方法
[root@test test]# cat function.sh
#!/bin/sh
fun1(){
echo "函数第一种定义方式"
}
function fun2 {
echo "函数第二种定义方式"
}
function fun3(){
echo "函数第三种定义方式"
}
fun1
fun2
fun3
[root@test test]# bash function.sh
函数第一种定义方式
函数第二种定义方式
函数第三种定义方式
[root@test test]#
函数的传参
[root@test test]# cat fun.sh
#!/bin/bash
fun() {
echo 1
echo "$1"
return 100
}
fun $1
[root@test test]# bash fun.sh 2
1
2
[root@test test]#
函数的本地变量local
[root@test test]# cat fun.sh
#!/bin/sh
fun1(){
local num=20
for i in `seq 30`
do
total=$[num+i]
done
echo "结果为:$total"
}
fun1
echo $num
[root@test test]# bash fun.sh
结果为:50
[root@test test]#
return 返回值
[root@test test]# cat fun.sh
#!/bin/sh
fun(){
if [ -f "$1" ];then
return 50
else
return 100
fi
}
fun $1
re=$?
[ $re -eq 50 ] && echo "$1 文件存在"
[ $re -eq 100 ] && echo "$1 文件不存在"
[root@test test]# bash fun.sh /etc/hosts
/etc/hosts 文件存在
[root@test test]# bash fun.sh /etc/hostsssssssss
/etc/hostsssssssss 文件不存在
[root@test test]#
数组
普通数组
只能使用数字作为索引
1) 数组的定义格式
第一种定义方式
数组名[索引名] 默认的索引从0开始
数组名[下标]=值
第二种定义方式
数组名=([0]=值 [1]=值 [20]=值)
第三种定义方式
array=(shell mysql kvm docker)
array=(shell mysql kvm docker [10]=test [20]=hehe)
2) 查看数组的值
echo ${array[0]}
3) 查看数组的索引
[root@backup ~]# echo ${!array[*]}
[root@test test]# cat array.sh
#!/bin/bash
IPS=(
114.114.114.114
223.5.5.5
)
for i in ${IPS[*]}
do
ping -c 1 -W 1 $i &>/dev/null
[ $? -eq 0 ] && echo ok|| echo error
done
[root@test test]# bash array.sh
ok
ok
[root@test test]#
关联数组
declare -A array 声明关联数组
[root@backup ~]# declare -A array
[root@backup ~]# array[index1]=shell
[root@backup ~]# array[index2]=MySQL
[root@backup ~]# array[index3]=KVM
[root@backup ~]# echo ${array[*]}
shell MySQL KVM
[root@backup ~]# echo ${!array[*]}
index1 index2 index3
[root@test test]# cat sex.txt
m
m
m
f
f
f
m
f
m
x
[root@test test]# cat array.sh
#!/bin/sh
declare -A array
while read line
do
let array[$line]++
done<sex.txt
for i in ${!array[*]}
do
echo "$i 出现了 ${array[$i]} 次"
done
[root@test test]# bash array.sh
f 出现了 4 次
m 出现了 5 次
x 出现了 1 次
[root@test test]#