Shell编程之循环语句(for、while、until)、函数(递归函数)、数组
Shell编程之循环语句[for、while、until]、函数(递归函数)、数组
一:循环语句
1.1:for循环语句
1.1.1:for语句结构
- 读取不同的变量值,用来逐个执行同一组命令
语句结构
for 变量名 in 取值列表
do
命令序列
done
语句结构举例
for 收件人 in 邮件地址列表
do
发送邮件
done
1.2:for语句应用示例
1.2.1:批量添加/删除用户
添加需求描述:
- 用户名存放再users.txt文件中,每行一个
- 初始密码均设为123456
- 验证脚本
[aaa@qq.com ~]# vim /opt/users.txt
tom
zhangsan
lisi
wangwu
[aaa@qq.com ~]# vim users.sh
#!/bin/bash
#定义
list=`cat /opt/users.txt`
#遍历集合
for user in $list
do
#添加用户
useradd $user
#设置密码
echo "123456" |passwd --stdin $user
echo "设置${user}用户成功"
done
[aaa@qq.com ~]# sh users.sh
更改用户 tom 的密码 。
passwd:所有的身份验证令牌已经成功更新。
设置tom用户成功
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
设置zhangsan用户成功
更改用户 lisi 的密码 。
passwd:所有的身份验证令牌已经成功更新。
设置lisi用户成功
更改用户 wangwu 的密码 。
passwd:所有的身份验证令牌已经成功更新。
设置wangwu用户成功
删除需求描述:
- 删除上面添加的用户
[aaa@qq.com ~]# vim users.sh
#!/bin/bash
#定义
list=`cat /opt/users.txt`
#遍历集合
for user in $list
do
#删除用户
userdel $user
#设置密码
#echo "123456" |passwd --stdin $user
echo "删除${user}用户成功"
done
[aaa@qq.com ~]# sh users.sh
删除tom用户成功
删除zhangsan用户成功
删除lisi用户成功
删除wangwu用户成功
1.2.2:根据IP地址检查主机状态
需求描述:
- IP地址存放再hosts.txt文件中,每行一个
- 使用ping命令检测各主机的连通性
[aaa@qq.com opt]# vim hosts.txt
20.0.0.1
192.168.100.100
20.0.0.2
[aaa@qq.com opt]# vim pingip.sh
#!/bin/bash
#定义集合
list=`cat /opt/hosts.txt`
#遍历集合
for IP in $list
do
ping -c 3 -i 0.2 -W 3 $IP > /dev/null
#条件判断
if [ $? -eq 0 ];then
echo "$IP is on"
else
echo "$IP is down"
fi
done
[aaa@qq.com opt]# sh pingip.sh
20.0.0.1 is down
192.168.100.100 is down
20.0.0.2 is on
1.2.3:用循环语句输出1-10的数字的方式
- 方式一:
#!/bin/bash
for ((i=1;i<=10;i++))
do
echo "$i"
done
- 方式二:
#!/bin/bash
i=1
for ((;i<=10;i++))
do
echo "$i"
done
- 方式三:
#!/bin/bash
i=1
for ((;i<=10;))
do
echo "$i"
let i++ done
- 方式四:
#!/bin/bash
i=1
for ((;;))
do
if [ $i -le 10 ];then
echo "$i"
let i++
else
exit 0
fi
done
- 四种方式输出结果都是一样的
[aaa@qq.com opt]# ./number.sh
1
2
3
4
5
6
7
8
9
10
1.2.4:shell中let命令
-
let 对整数进行数学运算
-
let和双小括号 (( )) 一样,let 命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。
语法格式
let 表达式
或
let "表达式"
或
let '表达式'
以上方式都等价于 ((表达式))
- 当表达式中含有 Shell 特殊字符(例如 |)时,需要用双引号
" "
或者单引号' '
将表达式包围起来。 - 和 (( )) 类似,let 命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let 命令的执行结果。
1.2.5:计算1-10偶数/奇数的和
方式一:计算偶数的和
#!/bin/bash
sum=0
#循环
for ((i=0;i<=10;i+=2))
do
let sum+=$i
done
echo "和为:${sum}"
[aaa@qq.comlocalhost opt]# sh oushuhe.sh
和为:30
计算奇数的和
#!/bin/bash
sum=0
#循环
for ((i=1;i<=10;i+=2))
do
let sum+=$i
done
echo "和为:${sum}"
[aaa@qq.comlocalhost opt]# sh oushuhe.sh
和为:25
方式二:求偶数和
#!/bin/bash
sum=0
for((i=0;i<=10;i+=2))
do
if [ `expr $i % 2` -eq 0 ]
then
let sum+=$i
fi
done
echo "总和为:$sum"
求奇数和
#!/bin/bash
sum=0
for((i=1;i<=10;i+=2))
do
if [ `expr $i % 2` -eq 1 ]
then
let sum+=$i
fi
done
echo "总和为:$sum"
1.3:while循环语句
1.3.1:while语句结构
- 重复测试某个条件,只要条件成立则反复执行
语句结构
while 条件测试操作
do
命令序列
done
语句结构示例
while 未猜中正确的价格
do
反复猜测商品价格
done
1.3.2:while语句应用示例
- 使用while循环语句输出1-10数字
#!/bin/bash
i=0
while [ $i -le 10 ]
do
echo "$i"
let i++
done
[aaa@qq.comlocalhost opt]# sh oushuhe.sh
0
1
2
3
4
5
6
7
8
9
10
1.3.3:while死循环
while true:死循环有时候也有奇效,可与用户交互
[aaa@qq.com opt]# vim sixunhuan.sh
#!/bin/bash
while true
do
read -p "输入yes继续/no退出:" way
if [ $way = no ];then
break
fi
done
echo "正常退出"
[aaa@qq.com opt]# sh sixunhuan.sh
输入yes继续/no退出:yes
输入yes继续/no退出:yse
输入yes继续/no退出:no
正常退出
1.3.4:使用while批量添加用户
需求描述:
- 用户名称以stu开头,按数字顺序进行编号
- 一共添加20个用户,即stu1,stu2…stu20
- 初始密码设置为123456
[aaa@qq.comlocalhost opt]# vim stu.sh
#!/bin/bash
i=1
while [ $i -le 20 ]
do
useradd stu$i
echo "123456" | passwd --stdin stu$i &> /dev/null
echo "设置stu${i}用户成功"
let i++
done
[aaa@qq.comlocalhost opt]# sh stu.sh
设置stu1用户成功
设置stu2用户成功
设置stu3用户成功
设置stu4用户成功
设置stu5用户成功
设置stu6用户成功
设置stu7用户成功
设置stu8用户成功
设置stu9用户成功
设置stu10用户成功
设置stu11用户成功
设置stu12用户成功
设置stu13用户成功
设置stu14用户成功
设置stu15用户成功
设置stu16用户成功
设置stu17用户成功
设置stu18用户成功
设置stu19用户成功
设置stu20用户成功
1.3.5:猜商品价格游戏
- 通过变量RANDOM获得随机数
- 提示用户猜测并记录次数,猜中后退出循环
[aaa@qq.com opt]# vim price.sh
#!/bin/bash
price=`expr $RANDOM % 100`
i=0
while true
do
read -p "请输入你认为的价格(1~100):" num
let i++
if [ $num -eq $price ];then
echo "恭喜你,猜对了"
echo "你输入的次数为:$i"
break
elif [ $num -gt $price ];then
echo "高了"
continue
else
echo "低了"
continue
fi
done
[aaa@qq.com opt]# sh price.sh
请输入你认为的价格(1~100):50
低了
请输入你认为的价格(1~100):75
高了
请输入你认为的价格(1~100):65
高了
请输入你认为的价格(1~100):55
低了
请输入你认为的价格(1~100):60
低了
请输入你认为的价格(1~100):63
高了
请输入你认为的价格(1~100):62
恭喜你,猜对了
你输入的次数为:7
1.4:continue和break
1.4.1:continue
命令格式
continue n
n 表示循环的层数:
如果省略 n,则表示 continue 只对当前层次的循环语句有效,遇到 continue 会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
如果带上 n,比如 n 的值为 2,那么 continue 对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带 n 的 continue。这么说可能有点难以理解,稍后我们通过代码来演示。
continue 关键字也通常和 if 语句一起使用,即满足条件时便跳出循环。
1.4.2:break
命令格式
break n
n 表示跳出循环的层数,如果省略 n,则表示跳出当前的整个循环。
break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
1.4.3:break、continue和exit的区别
break 用来结束所有循环,循环语句不再有执行的机会;continue 用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环;exit用来突出应用程序。
1.4.4:幸运抽奖
需求描述:
- 奖池观众:zhangsan,lisi,wangwu
- 奖池观众名单在name.txt中
- 共10次投票,使用随机数的方法挑选幸运观众,票数最多获胜
[aaa@qq.com opt]# vim name.txt
1:zhangsan
2:lisi
3:wangwu
[aaa@qq.com opt]# vim choujiang.sh
#!/bin/bash
B=0
C=0
D=0
for ((i=1;i<=10;i++))
do
A=$(expr $[RANDOM%3+1])
list=$(cat /opt/name.txt|grep "$A" |awk -F: '{print $2}')
case $list in
zhangsan)
let B++
;;
lisi)
let C++
;;
*)
let D++
esac
echo "$list"
done
[aaa@qq.com opt]# sh choujiang.sh
zhangsan
lisi
wangwu
lisi
zhangsan
lisi
wangwu
zhangsan
wangwu
wangwu
1.5:untli循环语句
1.5.1:until语句的结构
重复测试某个条件,只要条件不成立则反复执行
until 条件测试操作
do
命令序列
done
while 未超过 10
do
数字依次增加
done
1.5.2:until语句应用
示例一:计算1–50的和
需求描述:
- 通过循环累加的方式计算1–50的和
[aaa@qq.com opt]# vim he.sh
#!/bin/bash
i=1
sum=0
until [ $i -eq 51 ]
do
let sum+=$i
let i++
done
echo $sum
[aaa@qq.com opt]# sh he.sh
1275
示例二:为指定用户发送在线消息
需求描述:
- 若指定用户不在线(未登陆系统),则每10分钟(实验中为了测试效果,可改为3s)试一次,直至用户登录系统后再发送信息
- 用户名与消息通过为止参数传递给脚本
分析:
1.位置参数个数大于1
2.姓名属于系统用户 /etc/passwd
3.用户是否在线 until who
4.发送消息 echo"消息" | write 用户名
[aaa@qq.com opt]# vim xiaoxi.sh
#!/bin/bash
username=$1
#格式参数不能为空
if [ $# -lt 1 ];then
echo "Usage:`basename $0` <username> [<message>]"
exit 1
fi
#验证是否属于系统用户
if grep "^$username" /etc/passwd > /dev/null;then :
else
echo "no user"
exit 2
fi
#测试用户是否在线,如果不在线,每5s联系一次
until who | grep "$usrename" > /dev/null
do
echo "user not login"
sleep 5
done
#发送消息
echo "$2" | write "$username"
echo "${username}发送成功"
[aaa@qq.com opt]# sh xiaoxi.sh kevin hello
kevin发送成功
二:Shell函数
2.1:shell函数概述
-
shell一个非常重要的特性是它可作为一种编程语言来使用。
-
因为shell是一个解释器,所以它不能对为它编写的程序进行编译,而是在每次从磁盘加载这些程序时对它们进行解释。而程序的加载和解释都是非常耗时的。
-
针对此问题,许多shell(如BourneAgainShell)都包含shell函数,shell把这些函数放在内存中,这样每次需要执行它们时就不必再从磁盘读入。
-
shell还以一种内部格式来存放这些函数,这样就不必耗费大量的时间来解释它们
-
shell函数将命令序列按格式写在一起
-
可以方便重复使用命令序列
2.2:shell函数定义
[ function ] 函数名(){
命令序列
[return x] 使用return或exit可以显式地结束函数
}
其中,return返回的是状态码,需要使用$?调取
echo 返回的是值,使用变量调用
传参:指位置变量
可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
2.3:调用函数的方法
-
函数名 [参数1($1)] [参数2($2)]
-
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…
-
10不能获取第十个参数,获取第十个参数需要10 不能获取第十个参数,获取第十个参数需要10不能获取第十个参数,获取第十个参数需要{10}。当n>=10时,需要使用${n}来获取参数。
2.4:函数的作用范围
- 函数在Shell脚本中仅在当前Shell环境中有效
- Shell脚本中变量默认全局有效
- 将变量限定在函数内部使用local命令
示例
函数内部变量通过local来实现
- 通过定义score函数,在期内部设置局部变量i
- 函数内部和外部分别赋值,进行结果验证
#!/bin/bash
#求和函数体
function sum(){
#命令序列
read -p "请输入第一个整数:" num1
read -p "请输入第二个整数:" num2
SUM=$(($num1+$num2))
#echo 返回的是处理结果值
local score=100
echo "函数内$score"
echo "和:$SUM"
}
sum
echo "函数外$score"
2.5:shell函数应用
示例一:两个数字求和
需求描述:
- 通过sum(){}定义函数
- 使用read命令交互输入两个数并求和
[aaa@qq.com opt]# vim he.sh
#!/bin/bash
function sum(){
read -p "请输入第一个数字:" num1
read -p "请输入第二个数字:" num2
SUM=$[${num1}+${num2}]
echo "和为:$SUM"
}
sum
[aaa@qq.com opt]# sh he.sh
请输入第一个数字:10
请输入第二个数字:20
和为:30
示例二:编写登陆系统后便可使用的用户自定义函数
需求描述:
-
编辑用户自定义函数文件/root/function
-
在当前ShelIl中加载可执行的函数文件/root/function
-
在~/.bashrc文件中添加source /root/function命令
[aaa@qq.com ~]# vim ~/.bashrc
# .bashrc
# User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
function sum(){
#命令序列
read -p "请输入第一个整数:" num1
read -p "请输入第二个整数:" num2
SUM=$[$num1+$num2]
#echo返回的是处理结果值
echo "和:$SUM"
[aaa@qq.com ~]# source ~/.bashrc
[aaa@qq.com ~]# sum
请输入第一个整数:10
请输入第二个整数:320
和:330
2.6:函数的参数
2.6.1:参数的用法
函数名称 参数1 参数2 参数3 …
2.6.2:参数的表示方法
$1 $2 {10} ${11}…
2.7:递归函数
- 调用自己本身的函数
示例:递归遍历目录
需求描述:
- 通过定义递归函数list_files来实现
[aaa@qq.com opt]# vim files.sh
#!/bin/bash
function list_files(){
for f in `ls $1`
do
#判断是否为目录
if [ -d "$1/$f" ];then
echo "$2$f"
#递归调用
list_files "$1/$f" " $2"
else
echo "$2$f"
fi
done
}
list_files "/var/log" ""
[aaa@qq.com opt]# sh files.sh
anaconda
anaconda.log
ifcfg.log
journal.log
ks-script-E3s5jr.log
packaging.log
program.log
storage.log
syslog
X.log
audit
audit.log
boot.log
boot.log-20200609
boot.log-20200610
boot.log-20200611
boot.log-20200719
boot.log-20200720
boot.log-20200721
boot.log-20200722
btmp
btmp-20200719
chrony
cron
cron-20200609
cron-20200719
......
三:Shell数组
3.1:数组应用场景
- 获取数组长度
使用for循环获取具体信息,使用下标或索引标记数组中数据的位置
可以存放多种数据,如:整型,长整形,浮点型(单精度,双精度),字符串等
获取元素长度
-
遍历元素
-
元素切片
-
元素替换
-
元素删除
-
…
注意,忌讳数组越界,
数组下标从0开始算
数组长度从1开始算
长度为4,数组长度表示为1,2,3,4;数组下标表示为0,1,2,3
shell中数组是可变长的
3.2:数组定义方法
方法一:(推荐)
基本格式
数组名=(value0 value1 value2…)
例如
ABC=(11 22 33…)
方法二:
基本格式
数组名=([0]=value [1]=value [2]=value…)
例如
ABC=([0]=11 [1]=22 [2]=33…)
方法三:数组元素之间使用空格隔开
基本格式
列表名=“value0 value1 value2” 数组名=($列表名)
例如
AAA=“11 22 33…”
ABC=($列表名)
方法四:(多用于替换)
基本格式
数组名[0]=“value”
数组名[1]=“value”
数组名[2]=“value” …
例如 AAA[0]=“11”
AAA[1]=“22”
AAA[2]=“33”
…
3.3:数组包括的数据类型与数组操作
3.3.1:数组包括的数据类型
-
数值类型
-
字符类型
- 使用“”或‘’定义
数组只可存放数值或字符
3.3.2:数组操作
- 获取数组长度
基本格式
${#数组名[@/*]}
例如
[aaa@qq.com opt]# abc=(10 20 30)
[aaa@qq.com opt]# echo ${abc[*]}
10 20 30
[aaa@qq.com opt]# echo ${#abc[*]}
3
- 读取某下标赋值
基本格式
${数组名[下标]}
例如
[aaa@qq.com opt]# echo ${abc[1]}
20
[aaa@qq.com opt]# echo ${abc[0]}
10
[aaa@qq.com opt]# echo ${abc[1]}
20
[aaa@qq.com opt]# echo ${abc[2]}
30
数组遍历
[aaa@qq.com opt]# for v in ${arr_number[@]}
> do
> echo $v
> done
>
> 1
>
> 2
>
> 3
>
> 4
>
> 5
3.4:数组的应用
示例一:创建存放1~100奇数的数组
[aaa@qq.com opt]# vim a.sh
#!/bin/bash
j=0
k=1
for ((i=0;i<=99;i++))
do
j=$[$i+$k]
let k++
if [ $j -lt 100 ];then
arr[$i]=$j
fi
done
echo ${arr[*]}
[aaa@qq.com opt]# sh a.sh
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99
示例二:创建任意数字及长度的数组,根据客户需求加入元素
#!/bin/bash
#存入元素
i=0
while true
do
read -p "请输入第$[$i+1]元素:" j
arr[$i]=$j
read -p "请输入y/n:" guess
if [ $guess = y ];then
let i++
else
break
fi
done
echo ${arr[*]}
示例三:全班学生成绩未达到60分的,通过数组加到60分
#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
j=0
for ((i=0;i<${#score[*]};i++))
do
if [ ${score[$i]} -lt 60 ];then
new[$j]=60
else
new[$j]=${score[$i]}
let j++
fi
done
echo ${new[*]}
[aaa@qq.com opt]# sh score.sh
60 68 90 78 86 98 60
示例四:根据示例三中分数,比较出最大值
#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
temp=0
for ((i=0;i<${#score[*]};i++))
do
if [ ${score[$i]} -gt $temp ];then
temp=${score[$i]}
fi
done
echo $temp
[aaa@qq.com opt]# sh score.sh
98
示例五:根据示例三进行排序,由低到高
方法一:对比下标
#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
temp=0
for ((i=0;i<${#score[*]};i++))
do
for ((j=$i+1;j<${#score[*]};j++))
do
if [ ${score[$i]} -gt ${score[$j]} ];then
temp=${score[$i]}
let score[$i]=${score[$j]}
let score[$j]=temp
fi
done
done
echo ${score[*]}
方法二:找相对关系
#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
#外层为轮
for ((i=1;i<${#score[*]};i++))
do
#内存为次
for ((j=0;j<${#score[*]}-i;j++))
do #两数交换
if [ ${score[$j]} -gt ${score[`expr $j + 1` ]} ];then
tem=${score[`expr $j + 1`]}
score[`expr $j + 1`]=${score[$j]}
score[$j]=$tem
fi
done
done
echo ${score[*]}
[aaa@qq.com opt]# sh score.sh
48 56 56 59 60 68 78 86 90 98
3.5:Shell脚本调试
- echo命令
- bash命令
命令语法
sh [-nvx] 脚本名
常用选项
-n:不执行
-v:输出
-x:执行
- set命令
set -x:开启调节模式
set +x:关闭调节模式