第4章 循环语句和函数
编写一个好脚本的要素
逻辑结构定义清晰
脚本可以重用
多加入注释
学会调试脚本
echo配合exit命令或sleep命令
bash -x 脚本
知识要点
while语句、shift命令
case语句
shell函数应用
while语句的结构
重复测试某个条件,只要条件成立则反复执行
while语句的各种用法
注意:while通过管道,会产生一个新的bash(shell)
While语句应用示例
每5分钟输出一次时间到/tmp/time.txt
禁止使用计划任务
#!/bin/bash
while true //死循环
do
date +%H%M >> /tmp/time.txt
sleep 300
done
批量添加用户
用户名称以stu开头,按数字顺序进行编号
一共添加20个用户,即stu1、stu2、……、stu20
初始密码均设为123456
[root@localhost ~]# cat uaddwhile.sh
#!/bin/bash
PREFIX="stu"
i=1
while (( $i <= 20 )) //循环条件:序号<=20
do
useradd ${PREFIX}$i
echo "123456" | passwd --stdin ${PREFIX}$i &> /dev/null
(( i++ ))
done
[root@localhost ~]# ./uaddwhile.sh
[root@localhost ~]# grep "stu" /etc/passwd | tail -3
stu18:x:1028:1028::/home/stu18:/bin/bash
stu19:x:1029:1029::/home/stu19:/bin/bash
stu20:x:1030:1030::/home/stu20:/bin/bash
使用inotify-tools,实现自动同步备份
[root@localhost ~]# cat /opt/rsync.sh
inotifywait -mrq -e modify,create,move,delete /bak/ | while read DIR EVENT FILE
do
rsync -az --delete /bak/ rsync://10.10.10.1/share &>/dev/null
done
[root@localhost ~]# inotifywait -mrq -e modify,create,move,delete /bak/
/bak/ CREATE passwd
/bak/ MODIFY passwd
/bak/ DELETE passwd
分析当前主机中所有用户,哪些是普通用户,哪些是系统用户
#!/bin/bash
cat /etc/passwd | cut -d: -f1,3 | tr : ' ' > file1
while read user uid
do
if (( uid>=500 && uid<=600 ))
then
echo "$user是普通用户"
else
echo "$user是系统用户"
fi
done < file1
猜商品价格游戏
通过变量RANDOM获得随机数
提示用户猜测并记录次数,猜中后退出循环
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
TIMES=0
echo "商品实际价格为0-1000之间,猜猜看是多少?"
while true #循环条件:ture
do
read -p "请输入你猜测的价格数目:" INT
let TIMES++
if [ $INT -eq $PRICE ] ; then #提示猜测并记录次数
echo "恭喜你答对了,实际价格是 $PRICE"
echo "你总共猜测了 $TIMES 次"
exit 0 #若猜中则退出脚本
elif [ $INT -gt $PRICE ] ; then #与实际价格比较,给出提示
echo "太高了!"
else
echo "太低了!"
fi
done
[root@localhost ~]# ./pricegame.sh
商品实际价格为0-999之间,猜猜看是多少?
请输入你猜测的价格数目:500
太高了!
请输入你猜测的价格数目:250
太低了!
请输入你猜测的价格数目:375
太高了!
请输入你猜测的价格数目:280
太高了!
请输入你猜测的价格数目:265
太高了!
请输入你猜测的价格数目:253
恭喜你答对了,实际价格是 253
你总共猜测了 6 次
shift迁移语句
用于迁移位置变量,将 $1~$9 依次向左传递
例如,若当前脚本程序获得的位置变量如下:
$1=file1、$2=file2、$3=file3、$4=file4
则执行一次shift命令后,各位置变量为:
$1=file2、$2=file3、$3=file4
再次执行shift命令后,各位置变量为:
$1=file3、$2=file4
应用示例:
通过命令行参数传递多个整数值,并计算总和
[root@localhost ~]# vi test.sh
#!/bin/bash
n=1
while (($# > 0))
do
echo $$n is $1 && ((n++))
shift
done
应用示例:
通过命令行参数传递多个整数值,并计算总和
[root@localhost ~]# vi showday.sh
#!/bin/bash
Result=0
while (($# > 0))
do
Result=$((Result+$1))
shift
done
echo "The sum is : $Result“
[root@localhost ~]# ./sumer.sh 12 34 56
The sum is : 102
case语句的结构
针对变量的不同取值,分别执行不同的命令序列
case语句应用示例
击键类型识别
提示用户输入一个字符
判断出该字符是字母、数字或者其他字符
#!/bin/bash
read -p "请输入一个字符,并按Enter键确认:" KEY
case "$KEY" in
[a-z]|[A-Z])
echo "您输入的是 字母。"
;;
[0-9])
echo "您输入的是 数字。"
;;
*)
echo "您输入的是 空格、功能键或其他控制字符。"
esac
[root@localhost ~]# ./hitkey.sh
请输入一个字符,并按Enter键确认:k
您输入的是 字母 k 。
[root@localhost ~]# ./hitkey.sh
请输入一个字符,并按Enter键确认:8
您输入的是 数字 8 。
[root@localhost ~]# ./hitkey.sh
请输入一个字符,并按Enter键确认:^[[19~
您输入的是 空格、功能键或其他控制字符。
数字范围识别
判断分数范围,分出优秀、合格、不合格三档
[root@localhost ~]# cat test.sh
read -p "请输入您的分数(0-100):" GRADE
case "$GRADE" in
100|9[0-9]|8[5-9])
echo "$GRADE 分!优秀"
;;
8[0-4]|[67][0-9])
echo "$GRADE 分,合格" #大于等于85小于等于100
;;
*)
echo "$GRADE 分?不合格" #大于等于60小于等于84
;;
esac
编写系统服务脚本
使用start、stop、restart等参数来控制服务
服务控制指令通过位置变量$1传入
能够通过chkconfig命令来管理此服务
[root@localhost ~]# cat /etc/init.d/myprog
#!/bin/bash
# chkconfig: - 90 10 #用于chkconfig识别的配置
# description: Startup script for sleep Server
case "$1" in #根据$1传入的控制指令分别执行不同操作
start)
echo 正在启动$0服务
;;
stop)
echo 正在停止$0服务
;;
restart|reload)
$0 stop
$0 start
;;
*)
echo "用法: $0 {start|stop|restart}"
esac
Shell函数应用
Shell函数概述
- 在编写Shell脚本程序时,将一些需要重复使用的命令操作,定义为公共使用的语句块,即可称为函数
- 合理使用Shell函数,可以使脚本内容更加简洁,增强程序的易读性,提高执行效率
- 简化代码
- 维护成本低
- 可读性好
- 功能单一
- 功能模块化,方便协同合作
Shell函数由两部分组成:
函数名(在一个脚本中必须唯一)
函数体(命令或语句集合)
定义新的函数(必须先定义后使用)
调用已定义的函数
向函数内传递参数(可以使用位置参数)
Shell函数应用示例
应用示例:
在脚本中定义一个加法函数,用于计算2个整数的和
调用该函数计算(12+34)、(56+789)的和
#!/bin/bash
adder() {
echo $(($1 + $2))
}
adder 12 34
adder 56 789
[root@localhost ~]# ./adderfun.sh
46
845
新安装的mysql配置管理员密码的函数
默认新安装的mysql管理员密码为空
mysqlpass()
{
mysql -u root -e "use test;"
if (($?==0))
then
mysqladmin -uroot password '123' && echo "mysql密码修改成功"
else
echo "mysql无法修改密码"
exit 1
fi
}
检查rpm包的函数
如果rpm已经安装,则显示“包 is installed”
如果rpm包未安装,则用yum安装,安装好显示“包 install ok
checkRPM() {
for i #就是for i in $@获取所有位置参数
do
if ! rpm -q "$i" &> /dev/null
then
echo "please wait a moment"
yum install "$i" -y &> /dev/null
echo "$i install ok"
else
echo "$i is installed"
fi
done
}
checkRPM httpd mysql mysql-server php php-mysql
[root@localhost ~]# ./test.sh
httpd is installed
please wait a moment
mysql install ok
please wait a moment
mysql-server install ok
please wait a moment
php install ok
please wait a moment
php-mysql install ok
检查rpm包的函数
如果rpm已经安装,则显示“包 is installed”
如果rpm包未安装,则用yum安装,安装好显示“包 install ok”
如果希望是在运行脚本的时候输入位置参数
若要./test1.sh httpd mysql-server php php-mysql
checkRPM() {
for i #就是for i in $@获取所有位置参数
do
if ! rpm -q "$i" &> /dev/null
then
echo "please wait a moment"
yum install "$i" -y &> /dev/null
echo "$i install ok"
else
echo "$i is installed"
fi
done
}
checkRPM $@
[root@localhost ~]# ./22.sh httpd mysql mysql-server php php-mysql
httpd is installed
please wait a moment
mysql install ok
please wait a moment
mysql-server install ok
please wait a moment
php install ok
please wait a moment
php-mysql install ok
函数返回值
函数的退出状态有两种方式
默认退出状态:函数的最后一条命令返回的退出状态
使用return命令:以特定的退出状态退出函数
return只能在函数内部使用,默认是0
函数内部return之后的代码不会内执行
使用函数输出:直接将函数的结果赋值给变量
函数默认退出状态:函数的最后一条命令返回的退出状态
[root@localhost ~]# cat test1.sh
func() {
rpm -q sadfsdf &> /dev/null
echo '$?' is $?
ls /etc/passwd
}
func
echo func exit status is $?
[root@localhost ~]# bash test2.sh
$? is 1
/etc/passwd
func exit status is 0
[root@localhost ~]# cat test2.sh
func() {
ls /etc/passwd
echo '$?' is $?
rpm -q sadfsdf &> /dev/null
}
func
echo func exit status is $?
[root@localhost ~]# bash test2.sh
/etc/passwd
$? is 0
func exit status is 1
函数使用retun命令指定退出状态,只能用于函数返回值。
addUser()
{
for i in $@
do
if [[ $i == haha ]]; then
return 14 #return指定退出状态,后面的语句不会执行
else
useradd $i
fi
done
}
addUser $@
echo $?
[root@localhost ~]# ./test.sh xixi haha hehe
14
[root@localhost ~]# grep hehe /etc/passwd
[root@localhost ~]# #hehe没有创建
直接将函数的结果赋值给变量
[root@localhost ~]# cat test.sh
func ()
{
du -sh $1 | tr -d ' '| cut -d'/' -f1
}
size=$(func $1) #直接将函数的输出赋值给变量
echo $1目录占用空间是:$size
[root@localhost ~]# ./test.sh /etc
/etc目录占用空间是:33M
[root@localhost ~]# ./test.sh /usr/local
/usr/local目录占用空间是:140K
直接将函数的结果赋值给变量
函数比内部命令优先
num1(){
for i in $(seq $1) ; do
echo -n 1
done
}
num2(){
((j=32-$1))
for i in $(seq $j) ; do
echo -n 0
done
}
a=$(num1 $1)
b=$(num2 $1)
echo $a$b
函数中的变量
函数使用两种变量
- 全局变量:在函数内部定义的变量,当前脚本主代码可以获取,脚本主代码定义变量,函数内部也可以获取
- 局部变量:local 变量名,确保变量仅在函数内部使用 ,只能在函数内部使用
func ()
{
echo a is $a
b=100
}
a=200
func
echo b is $b
func ()
{
echo a is $a
local b=100
}
a=200
func
echo b is $b
函数库文件应用
函数库文件
如果多个脚本需要调用重复的函数,没必要在每个脚本中定义,只需要创建函数库文件,将需要的函数都放到这个库文件
每个脚本只需要一条语句调用库文件即可
注意不能把库文件当做普通脚本一样在脚本中运行,那样那些函数将不会出现在脚本中
可以存放公共
创建库文件
在脚本中调用库文件(注意库文件的路径)
还可以将库文件在.bashrc中定义
source /opt/lib.sh
全局函数可以在当前shell的所有子shell中随意运行
exprot -f port #将函数输出为全局函数
案例
实验案例1
编写脚本生成2位的随机数,要求个位和十位数不能相同,如果遇到个位和十位相同的就退出脚本(注意十位数不能为0)
[root@localhost ~]# exp1
61
57
81
51
51
65
31
连续成功7次
[root@localhost ~]# exp1
连续成功0次
[root@localhost test4]# exp1
23
连续成功1次
[root@localhost test4]# exp1
85
46
74
97
连续成功4次
实验案例2
给登录到当前主机的所有非root用户终端发送一句话“用户名: Hi,I’m Root!”
[root@localhost ~]# vi hello.sh
#!/bin/bash
who | grep -v ^root | tr -s ' ' | cut -d ' ' -f1,2 > file1
while read user tty
do
echo "$user : Hi , I'm Root!" > /dev/$tty
done < file1
实验案例3
根据前面猜价格的脚本,编写猜10以内的随机数的脚本,显示效果如下
每个人只有3次机会,超过3次没有猜中自动退出
实验案例4
编写del.sh脚本实现批量删除用户,脚本要求如下(使用while循环读取来改写上次的for脚本)
提示输入需要删除的用户名前缀,如果用户名前缀为空或者空格,就显示“请输入合法用户名前缀”,然后退出脚本,每删除一个用户,要显示“用户用户名 已经成功被删除”。如果没有可以删除的用户,就显示“以用户前缀开头的用户不存在”。最后要显示删除的用户总数是“一共新建的用户数:数目”
注意不能删除管理员或者系统用户(UID小500或者大于60000)
实验案例6
编写脚本显示如下图所示效果,要求选择一个菜单后,不用按回车,马上实现菜单相应的功能,显示完毕按任意键回到菜单,输入0退出菜单
、
***Menu***
1. Display disk space
2. Display interface information
3. Display memory usage
0. Exit menu
Enter option: 4
Sorry,wrong selection
Press any key to continue
***Menu***
1. Display disk space
2. Display interface information
3. Display memory usage
0. Exit menu
Enter option:
文件系统 容量 已用 可用 已用%% 挂载点
/dev/sda2 367G 9.6G 339G 3% /
tmpfs 1.9G 124K 1.9G 1% /dev/shm
/dev/sda1 97M 30M 63M 33% /boot
Press any key to continue
实验案例7
检查服务的函数,显示效果如图所示
检查服务状态,启动的要显示正常,未启动的要重启
重启后再次检查,还未启动的,要显示服务有问题
要检查服务名称是否错误
先停止FTP服务,然后故意改错FTP的配置文件
[root@localhost ~]# service vsftpd stop
关闭 vsftpd: [确定]
[root@localhost ~]# echo dsalkjf >> /etc/vsftpd/vsftpd.conf
[root@localhost ~]# ./test.sh smb vsftpd httpd
smb 状态正常
500 OOPS: missing value in config file for: dsalkjf
vsftpd服务有问题
httpd 状态正常
[root@servera test5]# ./test.sh httpd samba 服务名称错误
httpd重启后已经ok
samba服务不存在,请检查问题
实验案例8
检查侦听端口的函数,显示效果如图所示
输入一个参数检查侦听的端口,如果有侦听,显示端口和侦听的进程名称(要区分是udp还是tcp),如果端口没有侦听,则显示“端口未侦听”
[root@localhost ~]# ./test.sh 21
vsftpd正在侦听:tcp21端口
[root@localhost ~]# ./test.sh 980
rpcbind正在侦听:udp980端口
[root@localhost ~]# ./test.sh 53
53端口没有侦听
实验案例9
编写时间检查脚本
编写时间检查函数,算出当前时间离各个时间参照点的小时分钟差值,显示效果如下
2013年 07月 18日 星期四 08:40:00 CST
[root@servera opt]# ./time.sh
离上午上课还差0小时5分钟
[root@servera opt]# date -s 845
2013年 07月 18日 星期四 08:45:00 CST
[root@servera opt]# ./time.sh
上午上课时间到,躁起来,娭毑们
[root@servera opt]# date -s 1121
2013年 07月 18日 星期四 11:21:00 CST
[root@servera opt]# ./time.sh
离中午吃饭时间还有0小时24分钟
[root@servera opt]# date -s 1145
2013年 07月 18日 星期四 11:45:00 CST
[root@servera opt]# ./time.sh
开饭喽,同志们冲啊
[root@servera opt]# date -s 13:01
2013年 07月 18日 星期四 13:01:00 CST
[root@servera opt]# ./time.sh
午休,离下午上课时间还有0小时59分钟
[root@servera opt]# date -s 1539
2013年 07月 18日 星期四 15:39:00 CST
[root@servera opt]# ./time.sh
离放学时间还有1小时51分钟
实验案例10
编写获取配置文件中的数据的脚本
有多个脚本需要从配置文件中获取数据:
IP、端口、服务名、用户名、密码
写公共函数,通过参数来实现获取上面需要的dsN的数据
编写2个脚本,导入公共函数,然后调用 函数,获取数据