目录
一.shell循环-for语句
1.1for循环语法
for i in {取值范围} # for 关键字 i 变量名 in 关键字 取值范围格式 1 2 3 4 5
do # do 循环体的开始
循环体
done # done 循环体的结束
例如,循环输出1-100:
#!/usr/bin/env bash
#
# Author:
# Date: 2019/**/**
for i in {1..100}
do
echo $i
done
1.2并发执行
shell脚本的执行效率虽高,但当任务量巨大时仍然需要较长的时间,尤其是需要执行一大批的命令时。因为默认情况下,shell脚本中的命令是串行执行的。如果这些命令相互之间是独立的,则可以使用“并发”的方式执行这些命令,这样可以更好地利用系统资源,提升运行效率,缩短脚本执行的时间。如果命令相互之间存在交互,则情况就复杂了,那么不建议使用shell脚本来完成多线程的实现。
1.模拟场景
我们已知shell脚本中的命令是串行执行的,他会一个个依次执行循环中的内容,如果一个循环之中的事务处理时间较长,例如下面事务处理一个需要1秒,循环10次需要10秒,如果是更为庞大的数据,则会严重拖慢时间,降低效率。
#!/bin/bash
all_num=10 #定义一个变量为10
for num in `seq 1 ${all_num}` #依次循环,一共10次
do
sleep 1 #睡眠一秒,模拟事务执行的时间
echo ${num} #输出结果
done
一次一秒,依次输出结果:
1
2
3
4
5
6
7
8
9
10
由此,我们需要一个解决方法,来提高shell脚本在循环事务中的效率,以下为总结的一些方法供参考:
方案一:使用"&"使命令后台运行
在linux中,在命令的末尾加上&
符号,则表示该命令将在后台执行,这样后面的命令不用等待前面的命令执行完就可以开始执行了。循环体内可能有多条命令,则可以以{}
括起来,在大括号后面添加&
符号。例如:
#测试生产环境中ip地址的可用性
#!/usr/bin/env bash
# Author: #{}& 并发
>ip_alive.txt # 存放可用ip的文件
>ip_down.txt # 存放不可用ip的文件
a=$(date +%H%M%S) #定义一个记录时间的变量a
segment="192.168.161"
for i in {2..254}
do
{
ping -c1 $segment.$i &>/dev/null # 依次pingIP
if [ $? -eq 0 ];then
printf "alive: $segment.$i\n" >>ip_alive.txt #ping通则存放至文件夹
else
printf "down: $segment.$i\n" >>ip_down.txt #未ping通存放至另一文件
fi
}& #将上述内容并发执行
done
b=$(date +%H%M%S) #定义一个记录时间的变量a
echo -e "startTime:\t$a" #输出时间a
echo -e "endTime:\t$b" # 输出时间b
echo "finish..."
通过结果可知,没有一个个打印ip,而是直接先输出了时间a,b。而后一次性输出了所有ip结果,这是因为循环体内的命令全部进入后台,所以均在输出时间a,b运行完毕以后输出ip。这是由于循环体在后台执行,没有占用脚本主进程的时间。那么我们这里设置的a,b就没有实际意义了,这时候我们就要想办法,先让循环体运行结束,再执行循环体外的命令。
由此我们可以参考如下:
方案二:命令后台运行+wait
命令
解决上面的问题,只需要在上述循环体的done语句后面加上wait
命令,该命令等待当前脚本进程下的子进程结束,再运行后面的语句。
#!/usr/bin/env bash
# Author: #{}& 并发
>ip_alive.txt # 存放可用ip的文件
>ip_down.txt # 存放不可用ip的文件
a=$(date +%H%M%S) #定义一个记录时间的变量a
segment="192.168.161"
for i in {2..254}
do
{
ping -c1 $segment.$i &>/dev/null # 依次pingIP
if [ $? -eq 0 ];then
printf "alive: $segment.$i\n" >>ip_alive.txt #ping通则存放至文件夹
else
printf "down: $segment.$i\n" >>ip_down.txt #未ping通存放至另一文件
fi
}& #将上述内容并发执行
done
wait #等待前面命令执行完毕,再执行后面命令
b=$(date +%H%M%S) #定义一个记录时间的变量a
echo -e "startTime:\t$a" #输出时间a
echo -e "endTime:\t$b" # 输出时间b
echo "finish..."
但这样依然存在一个问题:
因为&
使得所有循环体内的命令全部进入后台运行,那么倘若循环的次数很多,会使操作系统在瞬间创建出所有的子进程,这会非常消耗系统的资源。如果循环体内的命令又很消耗系统资源,则结果可想而知。
最好的方法是并发的进程是可配置的。
方案三:使用xargs -P
控制并发数
xargs命令有一个-P
参数,表示支持的最大进程数,默认为1。为0时表示尽可能地大,即方案2
的效果。
#/bin/bash
all_num=10
thread_num=5
a=$(date +%H%M%S)
seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}"
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
方案四:使用GNU parallel
命令控制并发数
GNU parallel
命令是非常强大的并行计算命令,使用-j
参数控制其并发数量。
#/bin/bash
all_num=10
thread_num=6
a=$(date +%H%M%S)
parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10`
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
二.shell循环-while语句
2.1while循环语法
while 条件 # while 关键字 条件 [ $1 -lt 10 ] ,while循环,条件为真的情况下,会循环
do
循环体
done
举例:创建一个脚本,查看是否存在该用户,若无则创建该用户
#!/bin/bash
while read user
do
id $user &>/dev/null
if [ $? -eq 0 ];then
echo "$user is already exists"
else
useradd $user
echo "create $user successfully"
fi
done < user.txt
三.shell循环-until语句
3.1until循环语法
until 条件 # 当后面的条件表达式,为假的时候进行循环,当他为真了就停止循环了。
do
循环体
done
四.shift命令
4.1shift参数详解
位置参数可以用shift命令从左往右移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢 弃,$0不移动。不带参数的shift命令相当于shift 1。
对于位置变量或命令行参数,其个数必须是确定的,或者当 Shell 程序不知道其个数时,可以把所有参数一起赋值给变量 $*。
若用户要求 Shell 在不知道位置变量个数的情况下,还能逐个的把参数一一处理,也就是在 $1 后为 $2,在 $2 后面为 $3 等,则需要用shift把所有参数变成$1
举例:
#测试 shift 命令(x_shift3.sh)
[root@newrain shell]# cat x_shift3.sh
#!/bin/bash
shift #位置参数从左往右移动一位
echo "第一个位置参数: $1"
[root@newrain shell]# bash x_shift3.sh 2 3
第一个位置参数: 3
五.其他参数
5.1continue、break、exit命令
1.break
结束并退出本次循环
2.continue
在循环中不执行continue下面的代码,转而进入下一轮循环
3.exit
退出脚本,常带一个整数给系统,如 exit 0
可理解为:break是立马跳出循环;continue是跳出当前条件循环,继续下一轮条件循环;exit是直接退出整个脚本
例如:
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。 break命令允许跳出所有循环(终止执行后面的所有循环)。