目录
Shell 编程之循环语句与函数
for 循环语句
for 语句的结构
for 循环语句的语法结构
for 变量名 in 取值列表
do
命令序列
done
for 语句的执行流程:首先将列表中的第一个取值赋给变量,并执行 do…done 循环体中的命令序列;然后将列表中的第二个取值赋给变量,并执行循环体中的命令序列……依此类推,直到列表中的所有取值用完,最后将跳至 done 语句,表示结束循环,如下图所示。
for 循环语句的结构
for 语句应用示例
根据姓名列表批量添加用户
根据人事部门给出的员工姓名的拼音列表,在 Linux 服务器中添加相应的用户账号,初始密码均设置为“123456”。其中,员工姓名列表中的账号数量并不固定,而且除了要求账号名称是拼音之外,并无其他特殊规律。
针对上述要求,可先指定员工列表文件 users.txt,然后编写一个名为 uaddfor.sh 的 Shell脚本,从 users.txt 文件中读取各用户名称,重复执行添加用户、设置初始密码的相关操作。
[root@localhost ~]# vim /root/users.txt //用做测试的列表文件
chenye
dengchao
zhangjie
[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 ~]# tail -3 /etc/passwd
chenye:x:1005:1005::/home/chenye:/bin/bash
dengchao:x:1006:1006::/home/dengchao:/bin/bash
zhangjie:x:1007:1007::/home/zhangjie:/bin/bash
若要删除 uaddfor.sh 脚本所添加的用户,只需参考上述脚本代码,将 for 循环体中添加用户的命令序列改为删除用户的操作即可。例如,建立一个名为udelfor.sh 的脚本如下所示。
[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 +x udelfor.sh
[root@localhost ~]# ./udelfor.sh //测试并确认执行结果
[root@localhost ~]# id chenye
id: chenye: no such user //提示无此用户
while 循环语句
while 语句的结构
while 循环语句的语法结构如下所示。
while 条件测试操作
do
命令序列
done
while 语句的执行流程:首先判断 while 后的条件测试操作结果,如果条件成立,则执行 do…done 循环体中的命令序列;返回 while 后再次判断条件测试结果,如果条件仍然成立,则继续执行循环体;再次返回到 while 后,判断条件测试结果……如此循环,直到 while后的条件测试结果不再成立为止,最后跳转到 done 语句,表示结束循环。
图 3.2 while 循环语句的结构
使用 while 循环语句时,有两个特殊的条件测试操作,即 true(真)和 false(假)。使用 true 作为条件时,表示条件永远成立,循环体内的命令序列将无限执行下去,除非强制终止脚本(或通过 exit 语句退出脚本);反之,若使用 false 作为条件,则循环体将不会被执行。这两个特殊条件也可以用在 if 语句的条件测试中。
while 语句应用示例
批量添加规律编号的用户
在一些技术培训和学习领域,出于实验或测试的目的,需要批量添加用户账号,这些用户的名称中包含固定的前缀字串,并按照数字顺序依次进行编号,账号的数量往往也是固定的。例如,若要添加 20 个用户,名称依次为 stu1、stu2、…、stu20,可以参考以下操作。
[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 +x uaddwhile.sh
[root@localhost ~]# ./uaddwhile.sh
[root@localhost ~]# grep "stu" /etc/passwd | tail -3
stu18:x:1022:1022::/home/stu18:/bin/bash
stu19:x:1023:1023::/home/stu19:/bin/bash
stu20:x:1024:1024::/home/stu20:/bin/bash
until 循环语句
until 语句的结构
until 循环与 while 循环类似,while 循环能实现的脚本 until 同样也可以实现,但区别是while 循环在条件为真是继续执行循环,而 until 则是在条件为假时执行循环。
until 循环语句的语法结构如下所示。
until 条件测试操作
do
命令序列
done
until 语句的执行流程:首先判断 until 后的条件测试操作结果,如果条件不成立,则执行 do…done 循环体中的命令序列;返回 until 后再次判断条件测试结果,如果条件仍然不成立,则继续执行循环体;再次返回到 until 后,判断条件测试结果……如此循环,直到 until后的条件测试结果成立为止,最后跳转到 done 语句,表示结束循环。
图 3.3 until 循环语句的结构
until 语句应用示例
计算 1~50 的和
[root@localhost ~]# vim sum1to50_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 ~]# ./sum1to50_until_v1.sh
sum(1..50)=1275
Shell 函数
函数的用法
Shell 函数定义的方法如下所示:
[function] 函数名() {
命令序列
[return x]
}
- “function”关键字表示定义一个函数,可以省略;
- “{”符号表示函数执行命令的入口,该符号可以与函数名同行也可以在函数名下一行的句首;
- “}”符号表示函数体结束,两个大括号之间{ }是函数体;
- “命令序列”部分可以是任意的 Shell 命令,也可以调用其他函数;
- “return”表示退出函数返回一个退出值,通过返回值判断执行是否成功,也可以使用 exit 终止整个 Shell 脚本。
Shell 函数调用的方法为:函数名 [参数 1] [参数 2]。
1.两个数求和
使用 Shell 脚本实现两个数相加求和,通过定义函数的方式来完成。sum 函数内部通过read 命令接收用户分别输入的两个数,然后做加法运算,最后通过调用函数的方式来输出两个数的和。
[root@localhost ~]# vim sum.sh
#!/bin/bash
sum(){
read -p "请输入第一个数:" NUM1
read -p "请输入第二个数:" NUM2
echo “你输入的两个数为: $NUM1 和$NUM2.”
SUM=$(( NUM1+$NUM2))
echo “两个数的和为: $SUM”
}
sum
[root@localhost ~]# chmod +x sum.sh
[root@localhost ~]# ./sum.sh
请输入第一个数:2
请输入第二个数:3
“你输入的两个数为: 2 和 3.”
“两个数的和为: 5”
函数变量的作用范围
在 Shell 脚本中函数的执行并不会开启一个新的子 Shell,而是仅在当前定义的 Shell环境中有效。如果 Shell 脚本中的变量没有经过特殊设定,默认在整个脚本中都是有效的。在编写脚本时,有时需要将变量的值限定在函数内部,可以通过内置命令 local 来实现。函数内部变量的使用,可以避免函数内外同时出现同名变量对脚本结果的影响。local 命令的使用如下所示。
[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 ~]# ./fun_scope.sh
8
9
上述脚本中,myfun 函数内部使用了 local 命令设置变量 i,其作用是将变量 i 限定在函数内部。myfun 函数外部同样定义了变量 i,内部变量 i 和全局变量 i 互不影响。脚本执行时先调用了函数 myfun,函数内部变量 i 为 8,所以输出结果是 8。调用完函数之后,给变量i 赋值为 9,再打印外部变量 i,所以又输出 9。
函数的参数
函数的参数的用法如下。
函数名称 参数 1 参数 2 参数 3 ......
在使用函数参数时,函数名称在前参数在后,函数名和参数之间用空格分隔,可以有多个参数,参数使用$1、$2、$3……的方式表示。以此类推,从第 10 个参数开始,调用方法为${10},不加大括号无法调用成功。下面是函数参数的一个简单应用。
[root@localhost ~]# vim write_log.sh
#!/bin/bash
mydir="/data"
outfile="${mydir}/my.log"
[ -e "${mydir}" ] || mkdir -p ${mydir}
appendfile(){
echo "$2" >> "$1"
}
appendfile ${outfile} "first line content."
appendfile ${outfile} "second line content."
[root@localhost ~]# chmod +x write_log.sh
[root@localhost ~]# ./write_log.sh
[root@localhost ~]# cat /data/my.log
first line content.
second line content.
上述脚本接收两个参数,第一个参数是写日志的目标文件,第二个参数是日志信息,整个脚本实现将日志信息写入目标文件内的目的。
递归函数
Shell 也可以实现递归函数,就是可以调用自己本身的函数。在 Linux 系统上编写 Shell脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示。具体的实现过程如下所示。
[root@localhost ~]# vim fun_recursion.sh
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" ""
[root@localhost ~]# chmod +x fun_recursion.sh
[root@localhost ~]# ./fun_recursion.sh
anaconda
anaconda.log
program.log
storage.log
syslog
X.log
audit
audit.log
boot.log
btmp
cron
...... //省略部分内容
spooler
tallylog
tuned
tuned.log
wtmp
yum.log
Shell 数组
在 Shell 脚本中,数组是一种常见的数据结构,主要的应用场景包括:获取数组长度、获取元素长度、遍历元素、元素切片、元素替换、元素删除等等。Shell 中的数组与 Java、C、Python 不同,只有一维数组,没有二维数组。数组元素的大小与限制,也不需要事先定义。Shell 数组用括号()来表示,元素用空格分隔,元素的下标与大部分编程语言类似从 0 开始。
数组常用定义方法包括以下几种。
方法一:
数组名=(value0 value1 value2 ...)
方法二:
数组名=([0]=value [1]=value [2]=value ...)
方法三:
列表名=”value0 value1 value2 ...”
数组名=($列表名)
方法四:
数组名[0]=”value”
数组名[1]=”value”
数组名[2]=”value”
......
下面通过具体的示例掌握数组的基本使用方法。
(1)获取数组长度
[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
(2)读取某下标赋值
[root@localhost ~]# arr_index2=${arr_number[2]} //第三个元素
[root@localhost ~]# echo $arr_index2
3
(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 ~]# ./array_traverse.sh
1
2
3
4
5
(4)数组切片
[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
将数组切片之后,返回的是字符串,以空格作为分隔符。
(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
(6)数组删除
[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