1、循环控制语句
1.1 概述
语句 | 含义 | 应用场景 |
---|---|---|
exit | 终止执行脚本,退出返回值。exit n n的取值范围:0-255 | 基础用法,判断后加上exit 脚本结束加上exit 用于脚本中检查部分(参数个数,检查格式,adv:rc=0,可以放在脚本最后exit $rc) |
return | 放在函数中,终止执行函数,函数返回值 | 写在函数中,检查函数命令运行是否成功,方便测试 建议判断最后1个命令或函数中关键命令 |
break | 终止循环(退出),不会继续运行剩余循环 | 要在循环中表示退出循环 |
continue | 终止本次运行,进入下一次循环 | 要在循环中跳过某一次循环 |
break n:结束多少层循环,比如:break 3
continue n:结束当前循环,并从第几层开始,比如:continue 2
相关案例:
continue:
[root@m01 ~]#for i in {1..3}
do
for j in {a..c}
do
[ $j = b ] && continue
echo $i $j
done
done
1 a
1 c
2 a
2 c
3 a
3 c
[root@m01 ~]#
continue n:
[root@m01 ~]#for i in {1..3}
do
for j in {a..c}
do
[ $j = b ] && continue 2
echo $i $j
done
done
1 a
2 a
3 a
[root@m01 ~]#
continue:
[root@m01 ~]#for n in {1..5};do [ $n -eq 3 ] && continue; echo $n;done
1
2
4
5
[root@m01 ~]#
break:
[root@m01 ~]#for n in {1..5};do [ $n -eq 3 ] && break; echo $n;done
1
2
[root@m01 ~]#
break n:
[root@m01 ~]#for i in {1..3}
do
for j in {a..c};
do
[ $j = b ] && break 2
echo $i $j
done
done
1 a
[root@m01 ~]#for i in {1..3}
do
for j in {a..c}
do
[ $j = b ] && break
echo $i $j
done
done
1 a
2 a
3 a
[root@m01 ~]#
1.2 实战
案例:修改之前的猜数字脚本
如果输入不是数字,进行提示并继续提示用户输入
如果猜了10次还没成功,则结束
#1、random
num=$((RANDOM%100+1)) #RANDOM范围:1-32767,对100取余,再加1,得到1-100的数
i=0
percent() {
if [ $i -le 5 ];then
echo "超过了99.99%的人,猜了$i次"
elif [ $i -le 10 ];then
echo "超过了90%的人,猜了$i次"
else
echo "超过了85%的人,猜了$i次"
fi
}
input() {
#2、vars
read -p "输入数字:" guess
}
check() {
#3、check
expr $guess + 1 &> /dev/null
[ $? -gt 1 ] && {
echo "请输入数字,重来"
# exit 1
continue
}
}
compare() {
#4、compare
if [ $guess -eq $num ];then
echo "猜对了"
percent
exit
elif [ $guess -gt $num ];then
echo "数字大了"
else
echo "数字小了"
fi
}
main() {
#5、while
while true
do
input
let i++
check
compare
if [ $i -eq 5 ];then
echo "猜了5次都没正确,正确数字是:$num"
break
fi
done
}
main
测试:
[root@m01 /server/script/devops-shell]#sh 37.guess_num.sh
输入数字:80
数字大了
输入数字:60
数字大了
输入数字:50
数字大了
输入数字:30
数字小了
输入数字:40
数字大了
猜了5次都没正确,正确数字是:32
[root@m01 /server/script/devops-shell]#
案例02:return使用
[root@m01 /server/script/devops-shell]#vim +23 38.return.sh
function show() {
name=$1
cat <<EOF
show函数的参数个数:$#
show函数的所有参数:$*
${name}.com
${name}.cn
${name}.org
EOF
return $?
echo "这个会运行吗?"
}
show baidu.com
echo "函数的返回值:$?" #检查show函数执行是否正确
执行:
[root@m01 /server/script/devops-shell]#sh 38.return.sh
show函数的参数个数:1
show函数的所有参数:baidu.com
baidu.com.com
baidu.com.cn
baidu.com.org
函数的返回值:0
[root@m01 /server/script/devops-shell]#
2、数组
2.1 概述
数组也是一种变量
数组可以存放多个相关内容,通过访问数组调用结果(值)
应用场景:用于存放相关的数据
数组创建(手动):
数组名=(值)
ip_addr=(192.168.10.61 192.168.10.2) #以空格分隔
获取数组的值:
${数组命[元素名称或下标]}
例如:
echo ${ip_addr[0]}
注意:数组元素的下标从0开始。
如下:
[root@m01 ~]#ip_addr=(192.168.10.61 192.168.10.2)
[root@m01 ~]#echo ${ip_addr[0]}
192.168.10.61
[root@m01 ~]#echo ${ip_addr[1]}
192.168.10.2
[root@m01 ~]#echo ${ip_addr[2]}
[root@m01 ~]#
取出数组全部元素:”*、@“
[root@m01 ~]#echo ${ip_addr[*]}
192.168.10.61 192.168.10.2
[root@m01 ~]#echo ${ip_addr[@]}
192.168.10.61 192.168.10.2
[root@m01 ~]#
数组的元素个数(数组长度):#数组名[*或@]
[root@m01 ~]#echo ${#ip_addr[@]}
2
[root@m01 ~]#echo ${#ip_addr[*]}
2
[root@m01 ~]#
数组元素遍历:
[root@m01 ~]#for((i=0;i<${#ip_addr[*]};i++))
do
echo ${ip_addr[i]}
done
[root@m01 ~]#
或者:
[root@m01 ~]#for ip in ${ip_addr[*]}
do
echo $ip
done
[root@m01 ~]#
2.2 数组赋值
shell数组创建(赋值) | 形式 | 应用场景 |
---|---|---|
批量直接赋值 | array=(ip1 ip2 ip3) array=(命令结果) ,如:array=(cat ip.txt ) | 应用最多 |
逐个元素赋值 | array[0]=haha array[1]=hello… | 几乎不用 |
read命令赋值 | read -p ”请输入:” -a array(使用空格分隔) | 用于连续读取数据 |
例如:
[root@m01 /server/script/devops-shell]#cat ip.txt
192.168.10.61
192.168.10.1
192.168.10.2
192.168.10.7
www.baidu.com
baidu.com
[root@m01 /server/script/devops-shell]#ip_list=(`cat ip.txt`)
[root@m01 /server/script/devops-shell]#
[root@m01 /server/script/devops-shell]#users=(`awk -F: '{print $1}' /etc/passwd`)
[root@m01 /server/script/devops-shell]#echo ${#users[@]}
22
[root@m01 /server/script/devops-shell]#
read赋值:
[root@m01 ~]#read -p "输入数据:" -a num
输入数据:1 2 3
[root@m01 ~]#echo ${num[*]}
1 2 3
[root@m01 ~]#
行转列:
[root@m01 ~]#echo 1 2 3 | xargs -n1
1
2
3
[root@m01 ~]#
案例:写一个shell程序,然后求出其总和、平均值
在脚本中获取用户输入的数据
· read或$1、$2....
· 保存到文件
· 数组
脚本:
[root@m01 /server/script/devops-shell]#vim 38.cals_num.sh
#1、read
read -p "请输入数字(空格分隔,回车结束):" -a num
[[ "${num[*]}" =~ ^[0-9\ ]+$ ]] || {
echo "请输入数字"
exit 1
}
#2、cals
sum=0
for n in ${num[*]}
do
let sum=$sum+$n
done
avg=`echo "$sum/${#num[*]}" | bc -l`
max=`echo ${num[*]} | xargs -n1 | sort -rn | head -1`
min=`echo ${num[*]} | xargs -n1 | sort -rn | tail -1`
#3、echo
cat <<EOF
"所有数据:${num[*]}"
"总和:$sum"
"平均值:$avg"
"最大值:$max"
"最小值:$min"
EOF
执行脚本:
[root@m01 /server/script/devops-shell]#sh 38.cals_num.sh
请输入数字(空格分隔,回车结束):1 2 3
"所有数据:1 2 3"
"总和:6"
"平均值:2.00000000000000000000"
"最大值:3"
"最小值:1"
[root@m01 /server/script/devops-shell]#
cat EOF格式问题:
cat <<EOF
xxx
EOF(此处的EOF必须顶格写)
不顶格写:
cat <<-EOF(加上-)
xxx
EOF(此处不顶格写,必须用tab键,不能用空格)
取出数组元素最大、最小值的通用方法:
array=(2 3 1 4)
echo "原始数组:${array[*]}"
#取出元素最大、最小值
min=${array[0]} #把数组第一个元素作为初始值
max=${array[0]} #把数组第一个元素作为初始值
#min
for n in ${array[*]}
do
if [ $min -gt $n ];then
min=$n
fi
done
#max
for n in ${array[*]}
do
if [ $max -lt $n ];then
max=$n
fi
done
cat <<EOF
"最小值:$min"
"最大值:$max"
EOF
2.3 数组排序
#!/bin/bash
arr=(2 3 1 4)
echo "原始数组:${arr[*]}"
#sort
for((i=1;i<${#arr[*]};i++))
do
for((j=0;j<${#arr[*]}-i;j++))
do
if [ ${arr[j]} -gt ${arr[j+1]} ];then
temp=${arr[j]}
arr[j]=${arr[j+1]}
arr[j+1]=$temp
fi
done
echo "第$i次排序后:${arr[*]}"
done
echo "排序后:${arr[*]}"
3、堡垒机
shell脚本实现
功能:
1、登录堡垒机的机器,自动运行堡垒机脚本
2、显示菜单选择功能
1、远程连接
2、测试ping、telent等
不能给登录堡垒机的用户root权限
流程:
环境准备:
先配置好堡垒机到其他机器的秘钥认证。配置过程不再演示。
3.1、脚本
[root@m01 /server/script/devops-shell]#vim 39.baoleiji.sh
#0、trap
trap '' SIGINT
#1、main_menu主菜单
clear
main_menu() {
cat <<EOF
##########堡垒机#########
a:远程连接
b:测试工具
#########################
EOF
}
#远程连接菜单
ssh_menu() {
clear
cat <<EOF
1:连接192.168.10.5
2:连接192.168.10.7
EOF
}
#测试工具
test_menu() {
cat <<EOF
1:ping
2:telent
EOF
}
#2、input获取用户输入
input() {
read choice
}
#test
check_ping() {
read -p "请输入ping的ip:" ip
ping -c2 -i0.5 $ip &> /dev/null
[ $? -eq 0 ] && echo "$ip可以访问" || echo "无法访问"
}
check_telnet() {
read -p "请输入telent的ip 端口" ip port
echo q | telnet -eq $ip $port &> /dev/null
[ $? -eq 0 ] && echo "$ip $port可以访问" || echo "无法访问"
}
ssh_all() {
#远程连接
ssh_menu
input
case "$choice" in
1) ssh root@192.168.10.5 ;;
2) ssh root@192.168.10.7 ;;
*)
echo "选择错误,请重新输入"
continue
esac
}
telnet_all() {
#测试
test_menu
input
case "$choice" in
1) check_ping ;;
2) check_telnet ;;
*)
echo "选择错误"
continue
esac
}
#3、main
main() {
while true
do
main_menu
input
case "$choice" in
a|A)
ssh_all
;;
b|B)
telnet_all
;;
quit) break ;;
*)
echo error
continue
esac
done
}
main
执行脚本:
整体功能OK
存在的问题:
脚本需要手动执行
脚本一次性执行
3.2 脚本自动运行
把脚本放在/etc/profile.d/目录下即可。脚本名称要以.sh结尾
此处做个软连接作为调试用:
[root@m01 /server/script/devops-shell]#ln -sf /server/script/devops-shell/39.baoleiji.sh /etc/profile.d/
3.3 脚本禁止取消或结束
屏蔽ctrl+c
trap:获取指定的信号,用于获取指定操作产生的信号
ctrl+c对应的信号SIGINT
通过trap获取SIGINT信号(ctrl+c),获取后不执行任何任务。
trap '' SIGINT
把该命令放在脚本开头
4、脚本debug流程
书写习惯:
1、添加注释
2、变量:脚本中尽量使用变量,变量名要规范
3、函数:代码中尽量使用函数,增加说明
4、返回值:尽可能添加函数return功能,方便后期调试
5、参数与选项检查:尽可能增加exit返回值的功能,方便后期调试
6、书写的时候适当添加输出,通过临时的输出分析错误
7、缩进:代码注意缩进
8、成对的符号要提前输入好,以免忘记。如:[]、{}、''等成对的符号
调试方法
调试方法 |
---|
-x:大部分的时候可以使用,sh -x 脚本、bash -x 脚本,显示详细的执行过程。 |
精确显示过程:脚本使用functions函数库,脚本过大,可以在脚本中使用 set -x开始显示详细信息 代码 set +x关闭显示详细信息 |
注释法:通过注释,缩小范围定位问题 |
输出关键变量:在变量后增加echo,输出变量内容,查看过程 |
5、三剑客
5,1 sed与变量
案例1:sed命令中如何调用变量
ip.txt文件内容如下:
192.168.10.61
192.168.10.2
sed命令中使用变量
[root@m01 ~]#src=192.168.10
[root@m01 ~]#dst=172.16.1
[root@m01 ~]#sed "s#$src#$dst#g" ip.txt
172.16.1.61
172.16.1.2
[root@m01 ~]#
5.2 awk与变量
案例1:过滤出/etc/passwd的第2到第9行的第1和第3列
[root@m01 ~]#awk -F: 'NR>=2 && NR<=9{print $1,$3}' /etc/passwd | column -t
column -t:格式对齐
案例2:第1列用户名等于root的最后一列(命令解释器)
[root@m01 ~]#awk -F: '$1=="root"{print $NF}' /etc/passwd
/bin/bash
[root@m01 ~]#awk -F: '$1==root{print $NF}' /etc/passwd
[root@m01 ~]#
#$1==root的root没有加双引号,awk任务root是个变量,因此输出为空
使用变量:
[root@m01 ~]#name=root
[root@m01 ~]#awk -vn=$name -F: '$1==n{print $NF}' /etc/passwd
/bin/bash
[root@m01 ~]#awk -vn=$name -F: '$1~n{print $NF}' /etc/passwd
/bin/bash
[root@m01 ~]#
案例3:过滤网卡文件的ip地址那行
[root@m01 ~]#awk -F= '/IPADDR/{print $2}' /etc/sysconfig/network-scripts/ifcfg-eth0
192.168.10.61
[root@m01 ~]#
5.3 awk判断与循环
1)判断
awk中的if语句格式:
单分支:
if () {
语句
}
双分支:
if () {
语句
}
else {
语句
}
注意与shell脚本不一样。
例如:判断根分区磁盘使用率大于7%,提示磁盘空间不足
[root@m01 ~]#df -h | awk '$NF=="/"{ if($5>=7) print"磁盘空间不足"}'
磁盘空间不足
[root@m01 ~]#df -h / | awk -F "[ %]+" 'NR==2{if ($(NF-1)>=7) { print "磁盘空间不足";print $(NF-1)}}'
磁盘空间不足
7
[root@m01 ~]#
2)循环
例如:计算1到100的和
[root@m01 ~]#awk 'BEGIN{for(i=1;i<=100;i++){sum=sum+i} print sum}'
5050
[root@m01 ~]#awk 'BEGIN{
for(i=1;i<=100;i++)
{ sum=sum+i }
print sum
}'
5050
[root@m01 ~]#
案例2:输入数字,计算总和
[root@m01 ~]#read -p "请输入数字:" num
请输入数字:1 2 3
[root@m01 ~]#echo $num | awk '{for(i=1;i<=NF;i++) sum=sum+i ;print sum}'
6
[root@m01 ~]#
5.4 awk与数组
awk数组专用于统计与分析:去重统计次数(sort+uniq)、去重求和
awk数组与shell数组区别:
awk数组:关联数组,下标啥都行
shell数组:普通数组,下标是数字,shell中也有关联数组
使用:
创建数组:
[root@m01 ~]#awk 'BEGIN{ arr[0]="haha";arr["num"]=1 }'
取值:
[root@m01 ~]#awk 'BEGIN{ arr[0]="haha";arr["num"]=1; print arr["num"]}'
1
[root@m01 ~]#
5.5 awk数组循环
awk专用于数组的循环
for (n in 数组名)
print n(数组下标),数组名[n]
变量n:获取数组下标
数组名[下标] 才是对应的值
例如:
[root@m01 ~]#awk 'BEGIN{ arr[0]="haha";arr["num"]=1;
for(n in arr)
print n,arr[n]
}'
num 1
0 haha
[root@m01 ~]#
[root@m01 ~]#awk 'BEGIN{ arr[0]="haha";arr["num"]=1;
for(n in arr)
print n
}'
num
0
[root@m01 ~]#
案例1:批量赋值
awk-arr.txt文件内容如下:
blog.test.com 6
img.test.com 7
bbs.test.com 99
要求:创建以url为下标,元素值是次数数组,并输出
[root@m01 ~]#awk 'NR>=1{arr[$1]=$2}END{ for(url in arr) print url,arr[url] }' awk-arr.txt
blog.test.com 6
bbs.test.com 99
img.test.com 7
[root@m01 ~]#
案例2:去重统计次数
url.txt文件内容如下:
http://www.etiantian.org/index.html
http://www.etiantian.org/1.html
http://post.etiantian.org/index.html
http://mp3.etiantian.org/index.html
http://www.etiantian.org/3.html
http://post.etiantian.org/2.html
awk:
[root@m01 ~]#awk -F"[/.]+" '{arr[$2]=arr[$2]+1} END{for(n in arr) print n,arr[n]}' url.txt
www 3
mp3 1
post 2
[root@m01 ~]#
arr[$2]=arr[$2]+1可以写成:arr[$2]++
案例3:统计access.log中每个ip地址的流量总数
access.log文件:
第1列:ip
第10列:流量(单位:字节)
每列以空格分隔
awk:
[root@m01 ~]#awk '{liu[$1]=liu[$1]+$10} END{for(ip in liu) print ip,liu[ip]}' access.log | sort -rnk2 | head
liu[$1]=liu[$1]+$10可以写成:liu[$1]+=$10
5.6 awk与shell语法格式对比
具体语法 | awk | shell编程 |
---|---|---|
if单分支 | if(条件) { 命令 } | if [ 条件 ];then 命令 fi |
if双分支 | if(条件) { 命令 } else { 命令 } | if [ 条件 ] ;then 命令 else 命令 fi |
for循环,c语言格式 | for(i=1;i<=10;i++) { 命令 } | for(i=1;i<=10;i++) do 命令 done |
for循环:通用格式 | awk专用于数组的循环格式: for(n in 数组名) print n,数组名[n] | for n in 清单 do 命令 done |