小阿轩yx-Shell 编程之循环语句与函数
for 循环语句
可以很好地解决顺序编写异常烦琐、困难重重的全部代码
(){}:里边写的都是命令
):不能嵌套
$():可以嵌套,适合更复杂的命令
命令执行的两种方式
- 交互式:在命令行里直接写入命令,有多少需求就写入多少次命令
- 非交互式:把命令写入到脚本里去执行,人就不用在脚本里敲命令
(注:交互:人与机器之间的一种交流)
# 不想看到消息提示就放到(&>/dev/null黑洞)里面
echo "123456" | passwd --stdin $uname &>/dev/null
echo:作用回显(将内容显示到屏幕)
for 语句的结构
(注:for循环语句需要有一个取值列表)
for 变量名 in 取值列表
do
命令序列
done
根据姓名列表批量添加用户
# 创建用户的列表文件
[root@localhost ~]# vim /root/users.txt
zhangsan
lisi
wangwu
# 编辑批量添加用户的脚本
[root@localhost ~]# vim uaddfor.sh
#!/bin/bash
ULIST=$(cat /root/users.txt)
for UNAME in $ULIST
do
useradd $UNAME
echo "123456" | passwd --stdin $UNAME &>/dev/null
done
给脚本赋予执行权限
[root@localhost ~]# chmod +x uaddfor.sh
# 测试并确认执行结果
[root@localhost ~]# ./uaddfor.sh
[root@localhost ~]# cat /etc/passwd
编辑批量删除用户的脚本
[root@localhost ~]# vim udelfor.sh
#!/bin/bash
ULIST=$(cat /root/users.txt)
for UNAME in $ULIST
do
# 不想看到消息提示就放到黑洞里
userdel -r $UNAME &>/dev/null
done
# 给脚本赋予执行权限
[root@localhost ~]# chmod a+x udelfor.sh
# 测试并确认执行结果
[root@localhost ~]# ./udelfor.sh
[root@localhost ~]# cat /etc/pwd
根据 IP 地址列表检查主机状态
# 创建IP地址列表文件
[root@localhost ~]# vim /root/ipadds.txt
172.16.1.1
172.16.1.111
172.16.1.222
192.168.10.10
# 编辑循环检查各主机的脚本
[root@localhost ~]# vim chkhosts.sh
#!/bin/bash
HLIST=$(cat /root/ipadds.txt)
for IP in $HLIST
do
ping -c 3 -i 0.2 -W 3 $IP &> /dev/null
if [ $? -eq 0 ]
then
echo "Host $IP is up."
else
echo "Host $IP is down."
fi
done
# 添加执行权限
[root@localhost ~]# chmod a+x chkhost.sh
# 测试并确认执行结果
[root@localhost ~]# bash chkhosts.sh
- 需要指定一个变量及可能的取值列表
取值列表:称为 for 语句的执行条件,其中包括多个属性相同的对象,需要预先指定
- 针对每个不同的取值重复执行相同的命令序列
- 直到变量值用完退出循环从而不会进入死循环
使用 while 循环语句
可用于
- 求控制循环次数
- 操作对象按数字顺序编号
- 按特定条件执行重复操作
while 语句的结构
while 条件测试操作
do
命令序列
done
# 批量添加用户脚本
[root@localhost ~]# vim uaddwhile.sh
#!/bin/bash
PREFIX="stu"
i=1
while [ $i -le 20 ]
do
useradd ${PREFIX}$i
echo "123456" | passwd --stdin ${PREFIX}$i &> /dev/null
let i++
done
# 添加执行权限
[root@localhost ~]# chmod a+x uaddwhile.sh
# 测试并确认执行结果
[root@localhost ~]# bash uaddwhile.sh
[root@localhost ~]# grep "stu" /etc/passwd | tail -3
或者
#!/bin/bash
PREFIX="stu"
i=1
while [ $i -le 20 ]
do
useradd ${PREFIX}$i
echo "123456" | passwd --stdin ${PREFIX}$i &> /dev/null
i=`expr $i + 1`
done
# 批量删除用户脚本
[root@localhost ~]# vim udelwhile.sh
#!/bin/bash
PREFIX="stu"
i=1
while [ $i -le 20 ]
do
userdel -r ${PREFIX}$i
let i++
done
# 添加执行权限
[root@localhost ~]# chmod a+x udelwhile.sh
# 测试并确认执行结果
[root@localhost ~]# bash udelwhile.sh
id stu20
猜价格
[root@localhost ~]# vim pricegame.sh
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
TIMES=0
echo "商品实际价格范围为0-999,猜猜看是多少?"
while true
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
( 注:linux中随机数的取值范围是0--32767,和什么数取余,取余后的最大数就是谁,不包含该数字)
# 添加执行权限
[root@localhost ~]# chmod a+x pricegame.sh
# 测试并确认执行结果
[root@localhost ~]# bash pricegame.sh
- 不需要取值列表
- 可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。
- 在脚本应用中,要避免出现死循环的情况,否则后边的命令操作将无法执行。
- 因此,循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环
while 循环语句的两个结果(只有两个)
- true(真)
- false(假)
用 true 作为条件时
- 表示条件永远成立,循环体内的命令序列将无限执行下去,除非强制终止脚本(或通过 exit 语句退出脚本)
反之用 false 作为条件
- 则循环体将不会被执行
(注:这两个特殊条件也可以用在 if 语句的条件测试中)
until 循环语句
直到性循环
(注:一般处理一些有特色的)
until 循环语句的结构
until 条件测试操作
do
done
案例一
[root@localhost ~]# vim until_v1.sh
#!/bin/bash
i=0;s=0
until [ $i -eq 50 ]
do
let "i=$i+1";let "s=$s+$i"
done
echo 'sum(1..50)='$s
# 添加执行权限
[root@localhost ~]# chmod +x sum1to50_until_v1.sh
# 测试结果
[root@localhost ~]# bash sum1to50_until_v1.sh
案例二
[root@localhost ~]# vim until_v2.sh
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
# 添加执行权限
[root@localhost ~]# chmod +x until_v2.sh
# 测试结果
[root@localhost ~]# bash until_v2.sh
- 与 while 循环类似
- while 循环能实现的脚本 until 同样也可以实现
区别
- while 循环在条件为真时继续执行循环
- 而 until 则是在条件为假时执行循环
Shell 函数
代码的重用
local:作用将变量设置为局部变量
函数的用法
案例一
[function] 函数名() {
[return x]
}
[root@localhost ~]# cat exa1.sh
#!/bin/bash
zhangsan() {
echo "my name is zhangsan"
}
lisi() {
echo "my name is lisi"
}
zhangsan
lisi
# 测试结果
[root@localhost ~]# bash exa1.sh
案例二
[root@localhost ~]# cat exa2.sh
#!/bin/bash
name() {
echo "my name is $1"
}
name $1
# 测试结果
[root@localhost ~]# bash exa2.sh zhangsan
my name is zhangsan
[root@localhost ~]# bash exa2.sh lisi
my name is lisi
案例四
[root@localhost ~]# vim fun_scope.sh
myfun ()
{
local i
i=8
echo $i
}
i=9
myfun
echo $i
# 添加执行权限
[root@localhost ~]# chmod +x fun_scope.sh
# 测试结果
[root@localhost ~]# bash fun_scope.sh
(注:通过内置命令 local 将变量的值限定在函数内部)
Shell 脚本执行的过程中
- 函数被置于内存中
- 每次调用函数时不需要从硬盘读取,因此运行的速度比较快
Shell 编程中函数并非是必须的元素
使用函数的好处
- 可以对程序进行更好的组织
- 将一些相对独立的代码变成函数
- 可以提高程序可读性与重用性
- 避免编写大量重复代码
- “function”关键字表示定义一个函数,可以省略;
- “{”符号表示函数执行命令的入口,该符号可以与函数名同行也可以在函数名下一行的句首;
- “}”符号表示函数体结束,两个大括号之间{ }是函数体;
- “命令序列”部分可以是任意的 Shell 命令,也可以调用其他函数;
- “return”表示退出函数返回一个退出值,通过返回值判断执行是否成功,也可以使用 exit 终止整个 Shell 脚本
Shell 函数调用的方法为
函数名 [参数 1] [参数 2]
函数变量的作用范围
- Shell 脚本中函数的执行不会开启一个新的子 Shell,而是仅在当前定义的 Shell环境中有效
- 如果 Shell 脚本中的变量没有经过特殊设定,默认在整个脚本中都是有效的
- 编写脚本时,有时需要将变量的值限定在函数内部,可以通过内置命令 local 来实现
- 函数内部变量的使用,可以避免函数内外同时出现同名变量对脚本结果的影响
函数的参数
- 在使用函数参数时,函数名称在前参数在后,函数名和参数之间用空格分隔,可以有多个参数,参数使用$1、$2、$3……的方式表示
- 以此类推,从第 10 个参数开始,调用方法为${10},不加大括号无法调用成功
递归函数
递归算法的经典例子是计算阶乘
案例
[root@localhost ~]# vim fun_recursion.sh
#!/bin/bash
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$[ $1 - 1 ]
local result=$(factorial $temp)
echo $[ $result * $1 ]
fi
}
read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"
# 测试结果
[root@localhost ~]# bash fun_recursion.sh
调用自己本身的函数实现递归函数
Linux 系统上编写 Shell脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示
Shell 数组
Shell 脚本中,数组是一种常见的数据结构
主要的应用场景包括
数组常用定义方法包括以下几种
方法一
数组名=(value0 value1 value2 ...)
方法二
数组名=([0]=value [1]=value [2]=value ...)
方法三
列表名=”value0 value1 value2 ...”
数组名=($列表名)
方法四
数组名[0]=”value”
数组名[1]=”value”
数组名[2]=”value”
......
获取数组的长度
[root@localhost ~]# arr_number=(1 2 3 4 5)
[root@localhost ~]# arr_length=${#arr_number[*]}
[root@localhost ~]# echo $arr_length
5
[root@localhost ~]# arr_length_1=${#arr_number[@]}
[root@localhost ~]# echo $arr_length_1
5
读取某下标赋值
[root@localhost ~]# arr_index2=${arr_number[2]}
或
[root@localhost ~]# echo ${arr_number[2]}
//第三个元素
[root@localhost ~]# echo $arr_index2
3
或
echo ${aaa[3]}
数组遍历
[root@localhost ~]# vim array_traverse.sh
#!/bin/bash
arr_number=(1 2 3 4 5)
for v in ${arr_number[@]}
do
echo $v
done
# 添加执行权限
[root@localhost ~]# chmod +x array_traverse.sh
# 测试结果
[root@localhost ~]# bash array_traverse.sh
数组切片
[root@centos-7 ~]# arr=(1 2 3 4 5)
//输出整个数组
[root@centos-7 ~]# echo ${arr[@]}
1 2 3 4 5
//${数组名[@或*]:起始位置:长度}
[root@centos-7 ~]# echo ${arr[@]:0:2}
1 2
[root@centos-7 ~]# echo ${arr[@]:2:3}
3 4 5
数组替换
[root@centos-7 ~]# arr=(1 2 3 4 5)
// ${数组名[@或*]/查找字符/替换字符}
[root@centos-7 ~]# echo ${arr[@]/4/66}
1 2 3 66 5
// 并不会替换数组原有内容
[root@centos-7 ~]# echo ${arr[@]}
1 2 3 4 5
// 要实现改变原有数组,可通过重新赋值实现
[root@centos-7 ~]# arr=(${arr[@]/4/66})
[root@centos-7 ~]# echo ${arr[@]}
1 2 3 66 5
数组删除
[root@centos-7 ~]# arr=(1 2 3 4 5)
[root@centos-7 ~]# unset arr
// 删除数组
[root@centos-7 ~]# echo ${arr[*]}
[root@centos-7 ~]# arr=(1 2 3 4 5)
[root@centos-7 ~]# unset arr[2]
// 删除第三个元素
[root@centos-7 ~]# echo ${arr[*]}
1 2 4 5
(注:$* 和 $@ 都表示传递给函数或脚本的所有参数)
区别
- $* 和 $@ 不被双引号" "包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔
被双引号" "包含时,就会有区别
"∗ " 会 将 所 有 的 参 数 从 整 体 上 看 做 一 份 数 据 , 而 不 是 把 每 个 参 数 都 看 做 一 份 数 据
"@"仍然将每个参数都看作一份数据,彼此之间是独立的
Shell 脚本调试
[root@localhost ~]# vim aaa.sh
#!/bin/bash
set -x
##开启调试模式
read -p "请输入您的分数(0-100):" GRADE
if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ]
then
echo "$GRADE 分!优秀"
set +x
##关闭调试模式
elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ]
then
echo "$GRADE 分,合格"
else
echo "$GRADE 分?不合格"
fi
- 把复杂的脚本简单化
- 要思路清晰
- 分段实现
(注:执行脚本时出现错误后,不要只看提示的错误行,要观察整个相关的代码段)
为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。
echo 命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo 命令,采用的是分段排查的方式
(注:除了 echo 命令之外,bash Shell 也有相应参数可以调试脚本)
常用参数的具体含义为
- -n:不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题会提示报错
- -v:在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示
- -x:将执行的脚本内容输出到屏幕上,这个是对调试很有用的参数
小阿轩yx-Shell 编程之循环语句与函数