目录
引言
在实际工作中,经常会遇到某项目需要多次执行得情况,而每次执行时仅仅是处理的对象不一样,命令都是相同的。当面对各种列表重复任务时,使用简单的 if 语句很难满足要求,并且顺序编写全部代码更是显得异常繁琐,这时候就可以使用 for 循环语句,可以很好的解决这样的问题。
一、使用 for 循环语句
1. for 语句的结构
- 使用 for 循环语句时,需要指定一个变量及可能的取值列表,针对每一个不同的取值重复执行相同的命令序列,直到变量值用完退出循环。
- for 循环语句的语法结构如下
for 变量名 in 取值列表
do
命令序列
done
- 上述语句结构中,操作对象为用户指定名称的变量,并通过 in 关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于 do 和 done 之间的命令序列称为“循环体”,其中的执行语句需要引用变量以完成相应的任务。
2. for 语句的执行流程
- 首先将列表中的第1个取值赋给变量,并执行 do···done 循环体中的命令序列
- 然后将列表中的第2个取值赋给变量,并执行循环体中的命令序列……
- 以此类推,直到列表中的所有取值用完,最后将跳至 done 语句,表示结束循环
3. for 语句应用示例
- 根据姓名列表批量添加用户,操作如下
[root@localhost /home]#vim /opt/user.txt #用户列表文件
wangwu
zhangsan
lisi
[root@localhost /home]#vim fo2.sh #批量添加或删除用户脚本
#!/bin/bash
ULIST=$(cat /opt/user.txt)
for UNAME in $ULIST
do
useradd $UNAME
# userdel -r $UNAME &>/dev/null
echo "123456" |passwd --stdin $UNAME &>/dev/null
echo "创建用户成功"
# echo "删除用户中...";sleep 2
# echo "删除成功"
done
wq保存并退出
#如果想要批量删除用户就可以把上面注释去掉,并把创建用户的命令给注释掉
[root@localhost /home]#. fo2.sh #测试并确认执行的结果
创建用户成功
创建用户成功
创建用户成功
[root@localhost /home]#tail -3 /etc/passwd
wangwu:x:1001:1001::/home/wangwu:/bin/bash
zhangsan:x:1002:1002::/home/zhangsan:/bin/bash
lisi:x:1003:1003::/home/lisi:/bin/bash
- 根据 IP 地址列表检查主机状态
[root@localhost /home]#vim /opt/ipadds.txt #IP地址列表文件
192.168.8.1
192.168.8.2
192.168.8.3
[root@localhost /home]#vim fo3.sh #循环检查各主机的脚本
#!/bin/bash
HLIST=$(cat /opt/ipadds.txt)
for IP in $HLIST
do
ping -c 3 -i 0.2 -W 3 $IP &>/dev/null #-c是发送ping包的数量为3
if [ $? -eq 0 ] #-i是收发信息的间隔时间为0.2s
then #-W是超时时间为3s
echo "HOST $IP is up"
else
echo "HOST $IP is down"
fi
done
[root@localhost /home]#sh fo3.sh #使用sh命令就不用赋予权限才能运行
HOST 192.168.8.1 is up
HOST 192.168.8.2 is up
HOST 192.168.8.3 is down
- 密码输入正确和错误提示
[root@localhost /home]#vim fo4.sh
#!/bin/bash
init=123456
for i in {1..3}
do
read -p "请输入用户密码:" passwd
if [ $passwd == $init ]
then
echo "密码正确"
exit
fi
done
echo "密码错误"
[root@localhost /home]#sh fo4.sh
请输入用户密码:1
请输入用户密码:1
请输入用户密码:1
密码错误 #密码输入错误三次直接退出交互界面
[root@localhost /home]#sh fo4.sh
请输入用户密码:123456
密码正确 #密码输入成功提示密码输入正确
二、while 循环语句
for 循环语句非常适合于列表对象无规律,且列表来源已固定(如某个列表文件)的场合。而对于要求控制循环次数、操作对象按数字顺序编号、按特定条件执行重复操作等情况,则更适合使用 while 语句。
1. while 语句的结构
-
使用 while 循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。但是在脚本应用中,应该避免出现死循环的情况,否则后边的命令操作将无法执行。
-
循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环。
-
while循环语句的语法结构如下所示:
while 条件测试操作
do
命令序列
done
2. while 语句的执行流程
- 首先判断 while 后的条件测试操作结果,如果条件成立,则执行 do···done 循环体中的命令序列
- 返回 while 后再次判断条件测试结果,如果条件仍然成立,则继续执行循环体
- 再次返回到 while 后,判断条件测试结果…如此循环
- 直到 while 后的条件测试结果不再成立为止,最后跳转到 done 语句,表示结束循环
3. while 语句应用示例
- ① 批量添加规律编号的用户
[root@localhost /home]#vim useradd.sh #批量添加用户脚本
#!/bin/bash
PREFIX="gl"
i=1
while [ $i -le 20 ]
do
useradd ${PREFIX}$i
echo "123456"|passwd --stdin ${PREFIX}$i &>/dev/null
let i++
done
[root@localhost /home]#sh useradd.sh
[root@localhost /home]#grep "gl" /etc/passwd |tail -3
gl18:x:1021:1021::/home/gl18:/bin/bash
gl19:x:1022:1022::/home/gl19:/bin/bash
gl20:x:1023:1023::/home/gl20:/bin/bash
上述脚本代码中,使用变量 i 来控制用户名称的编号,初始赋值为1,并且当取值大于20时终止循环。在循环中,通过语句 “let i++”(等同于i=‘expr $i+1’)来使变量i的值增加1,因此当执行第一次循环后i的值将变成2,执行第2次循环后i的值将变成3…以此类推。
- 如果想要删除添加的用户,只需要把 while循环中添加用户的命令序列改为删除用户的操作即可,操作如下:
[root@localhost /home]#vim useradd.sh
#!/bin/bash
PREFIX="gl"
i=1
while [ $i -le 20 ]
do
userdel -r ${PREFIX}$i
# echo "123456"|passwd --stdin ${PREFIX}$i &>/dev/null
let i++
done
[root@localhost /home]#sh useradd.sh
[root@localhost /home]#id gl20 #确认用户是否删除成功
id: gl20: no such user
- ② 猜数字大小
[root@localhost /home]#vim w4.sh
#!/bin/bash
NUM=8
while true
do
read -p "请输入数字:" sz
if [ $sz -eq $NUM ];then
echo "你猜对了!"
break
elif [ $sz -gt $NUM ];then
echo "你猜大了"
elif [ $sz -lt $NUM ];then
echo "你猜小了"
fi
done
[root@localhost /home]#sh w4.sh
请输入数字:2
你猜小了
请输入数字:4
你猜小了
请输入数字:6
你猜小了
请输入数字:8
你猜对了!
- ③ 猜价格游戏
[root@localhost /home]#vim w5.sh
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
a=0
echo "商品实际价格范围为0~999,猜猜价格多少?"
while true
do
read -p "请输入你猜测的价格:" INT
let a++
if [ $INT -eq $PRICE ];then
echo "恭喜你猜对了,实际价格是$PRICE"
echo "你总共猜了 $a次"
break
elif [ $INT -gt $PRICE ];then
echo "你猜高了"
else
echo "你猜低了"
fi
done
[root@localhost /home]#sh w5.sh
商品实际价格范围为0~999,猜猜价格多少?
请输入你猜测的价格:188
你猜低了
请输入你猜测的价格:288
你猜低了
...... 省略
你猜高了
请输入你猜测的价格:920
恭喜你猜对了,实际价格是920
你总共猜了 12次
三、Shell 函数
1. 概述
- 在编写shelI脚本时,你经常会发现在多个地方使用了同一段代码。如果只是一小段代码,一般也无关紧要。但是在shell脚本中多次重写大块代码段就太累人了。
- 为了解决这个问题,我们可以将 shell 脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次重复使用它了。
2. 函数的基本格式
[function] 函数名(){
命令序列
[return x] #return x 的作用是命令序列执行完后返回给系统一个值,可省略
}
或者
#也可以省略掉[function],它表示该函数的功能
函数名() { #函数名后面()是没有内容的
命令序列 #我们执行的命令内容放在{}里面
}
- 函数一结束就取返回值,退出的状态码必须是0~255
- 由于退出状态码必须小于256,函数的结果必须生成一个小于256的整数值,任何大于256的值都会产生一个错误值。
3. 函数应用示例
- 定义一个输入两个数求和的脚本
[root@localhost /home]#vim summ.sh
#!/bin/bash
sum(){
read -p "请输入第一个数:" NUM1
read -p "请输入第二个数:" NUM2
echo "你输入的二个数为:$NUM1 和 $NUM2."
SUM=$[$NUM1 + $NUM2]
echo "两个数和为:$SUM"
}
sum
[root@localhost /home]#sh summ.sh
请输入第一个数:8
请输入第二个数:9
你输入的二个数为:8 和 9.
两个数和为:17
- 返回值的范围
[root@localhost /home]#vim retun.sh
#!/bin/bash
function test1 {
read -p "请输入数字:" num
return $[num*2]
}
test1
echo $?
[root@localhost /home]#sh retun.sh
请输入数字:3
6
[root@localhost /home]#sh retun.sh
请输入数字:127
254
[root@localhost /home]#sh retun.sh
请输入数字:128
0
- 函数的阶乘
[root@localhost /home]#vim cc.sh
#!/bin/bash
fa () {
if [ $1 -eq 1 ]
then
echo 1
else
local tp=$[ $1 - 1 ]
local res=$(fa $tp)
echo $[ $1 * $res ]
fi
}
read -p "请输入:" num
res=$(fa $num)
echo $res
[root@localhost /home]#sh cc.sh #5*4*3*2*1=120
请输入:5
120
[root@localhost /home]#sh cc.sh #3*2*1=6
请输入:3
6
四、数组
1. 定义数组
- 可以在单行中使用数值列表来定义一个数组
array_var=(test1 test2 test3 test4)
#这些值将会存储在以0为起始索引的连续位置上
或者
#将数组定义为一组“索引—值”
array_var=[0]="test1"
array_var=[1]="test2"
array_var=[2]="test3"
array_var=[3]="test4"
array_var=[4]="test5"
array_var=[5]="test6"
- 精确的给每一个下标索引定义一个值加入数组,索引数字可以不连续
num=([0]=55 [1]=66 [2]=77 [4]=88)
数组名=([0]=value [1]=value [2]=value. . .)
- 先把要加入数组的元素全部先赋值给一个变量,然后引用这个变量加入到数组
list="11 12 13 14"
num=($list)
2. 获取数组长度
- 打印出特定索引的数组元素内容
#echo ${array_var [0]}
test1
index=5
#echo ${array_var[$index]}
test6
- 以列表形式打印出数组中的所有值
#echo ${array_var[*]}
test1 test2 test3 test4 test5 test6
或者
#echo ${array_var[@]}
test1 test2 test3 test4 test5 test6
- 打印数组长度(即数组中元素的个数)
#echo ${#array_var[*]}6
- 数组元素的遍历
[root@localhost ~]#arr=(1 2 3 4 5 6)
[root@localhost ~]#for i in ${arr[*]}
> do
> echo $i
> done
1
2
3
4
5
6
3. 元素切片
[root@localhost ~]#arr=(1 2 3 4 5 6 7 8 9)
[root@localhost ~]#echo ${arr[*]}
1 2 3 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]:2:3} #提取出第二个数后面的三个值
3 4 5
[root@localhost ~]#echo ${arr[*]:2:5}
3 4 5 6 7
[root@localhost ~]#echo ${arr[*]:2:2}
3 4
[root@localhost ~]#echo ${arr[*]:0:6}
1 2 3 4 5 6
4. 元素替换
[root@localhost ~]#arr=(1 2 3 4 5 6 7 8 9)
[root@localhost ~]#echo ${arr[*]}
1 2 3 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]/3/88} #3指的是查找数主里面的字符,88指的是需要替换的字符
1 2 88 4 5 6 7 8 9
[root@localhost ~]#echo ${arr[*]/5/66}
1 2 3 4 66 6 7 8 9
5. 元素删除
[root@localhost ~]#arrt=(1 2 33 4 566 6)
[root@localhost ~]#echo ${arrt[*]}
1 2 33 4 566 6
[root@localhost ~]#unset arrt[2] #删除的时候是以下标的形式来进行删除,下标2对应的字符为33
[root@localhost ~]#echo ${arrt[*]}
1 2 4 566 6
[root@localhost ~]#unset arrt[4] #下标4对应的字符为566
[root@localhost ~]#echo ${arrt[*]}
1 2 4 6
6. 冒泡排序
-
概念:
指的是它会遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止。 -
案例
[root@localhost /home]#vim mp.sh
#!/bin/bash
array=(20 40 30 10 60 50)
echo "old_array:${array[*]}"
lt=${#array[*]}
#定义比较轮数,比较轮数为数组长度减1,从1开始;
for ((i=1;i<$lt;i++))
do
#确定比较元素的位置,比较相邻的两个元素,较大的数往后放,小的数往前放,并且每
轮比较次数需要随着轮数递减
for ((j=0;j<$lt-i;j++))
do
#定义第一个元素的值
first=${array[$j]}
#定义第二个元素的值
k=$[$j+1]
second=${array[$k]}
#如果第一个元素比第二个元素大,就进行互换
if [ $first -gt $second ]
then
#把第一个元素值存放到临时变量中
tmp=$first
#把第二个元素值赋给第一个元素
array[$j]=$second
#把临时变量(第一个元素原值)赋给第二个元素
array[$k]=$tmp
fi
done
done
echo "new_array:${array[*]}"
下面以数列{20,40,30,10,60,50}为例,演示它的冒泡排序过程:
- 流程如下:
① 我们先分析第1趟排序
当i=5,j=0时,a[0]<a[1];此时,不做任何处理。
当i=5,j=1时,a[1]>a[2];此时,交换a[1]和a[2]的值;交换之后,a[1]=30,a[2]=40。
当i=5,j=2时,a[2]>a[3];此时,交换a[2]和a[3]的值;交换之后,a[2]=10,a[3]=40。
当i=5,j=3时,a[3]<a[4];此时,不做任何处理!
当i=5,j=4时,a[4]>a[5];此时,交换a[4]和a[5]的值;交换之后,a[4]=50,a[3]=60。
② 第1趟排序完之后,数列 {20,40,30,10,60,50} 变成了 {20,30,10,40,50,60}。此时,数列末尾的值最大。
③ 根据这种方法可以知道:
第2趟排序完之后,数列中 a[5…6] 是有序的。
第3趟排序完之后,数列中 a[4…6] 是有序的。
第4趟排序完之后,数列中 a[3…6] 是有序的。
第5趟排序完之后,数列中 a[1…6] 是有序的。
第5趟排序之后,整个数列也就是有序的了。
- 上面排序的输出结果如下
[root@localhost /home]#sh mp.sh
old_array:20 40 30 10 60 50
new_array:10 20 30 40 50 60
- 方法2,操作如下
[root@localhost /home]#vim mp1.sh
#!/bin/bash
NUM=(90 70 60 40 50 30)
echo "old_NUM:${NUM[*]}"
for ((i=0;i<${#NUM[*]};i++))
do
for ((j=i+1;j<${#NUM[*]};j++))
do
if [ ${NUM[$i]} -gt ${NUM[$j]} ]
then
temp=${NUM[$i]}
NUM[$i]=${NUM[$j]}
NUM[$j]=$temp
fi
done
done
echo "new_NUM:${NUM[*]}"
[root@localhost /home]#sh mp1.sh
old_NUM:90 70 60 40 50 30
new_NUM:30 40 50 60 70 90
五、Shell 脚本调试
-
在Shell 脚本开发中,经常碰到一些规范方面的问题,例如忘了便用引号或在 if 语句末尾处忘记加 fi 结束。
-
要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现脚本错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。
-
为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。
1. echo 命令
echo命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo 命令,采用的是分段排查的方式。
2. bash 命令
-
除了echo 命令之外,bash Shell 也有相应参数可以调试脚本。
-
使用 bash 命令参数调试,命令的语法如下:
sh [-nvx] 脚本名
- 常用的参数如下所示
① -n:不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题会提示报错。
② -V:在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示。
③ -x:将执行的脚本内容输出到屏幕上,这是个对调试很有用的参数。
3. set 命令
-
当脚本较长时,可以使用 set 命令指定调试一段脚本
-
常用参数如下:
set -x #开启调试模式
set +x #关闭调试模式
- 示例
[root@localhost /home]#cat 1.sh
#!/bin/bash
set -x
for ((i=1;i<=5;i++))
do
echo $i
done
#开启调试模式以后会看到每段的反馈结果
[root@localhost /home]#sh 1.sh
+ (( i=1 ))
+ (( i<=5 ))
+ echo 1
1
+ (( i++ ))
+ (( i<=5 ))
+ echo 2
2
+ (( i++ ))
+ (( i<=5 ))
+ echo 3
3
+ (( i++ ))
+ (( i<=5 ))
+ echo 4
4
+ (( i++ ))
+ (( i<=5 ))
+ echo 5
5
+ (( i++ ))
+ (( i<=5 ))
总结
Shell 脚本是千变万化的,正所谓条条大路通罗马,shell 脚本中也是如此。一个脚本可以有无数种写法,但是思路都是相通的,只要有了思路脚本就自然会写的很顺畅!