Shell模拟多线程

shell不能实现多线程,但是可以通过限制几乎同时放入后台执行的进程数量来模拟多线程,从而达到在提高脚本执行效率的同时又不明显增加负载的作用。

Ping脚本的多线程实现

#!/bin/bashset -x#开启调试模式# Usage:# History:#thread=$1#设置线程数,在这里所谓的线程,其实就是几乎同时放入后台(使用&)执行的进程。if [ "$1"x == ""x ]; thenthread=1fitmp_fifofile=/tmp/$$.fifo#脚本运行的当前进程ID号作为文件名mkfifo $tmp_fifofile#新建一个随机fifo管道文件exec 6<>$tmp_fifofile#定义文件描述符6指向这个fifo管道文件rm $tmp_fifofile#清空管道内容#定义一个函数做为线程(子进程),该函数功能是ping测试function func(){ping -c 3 $ip &>/dev/null && r=0 || r=1if [ $r -eq 0 ]; thenecho "$ip ok"elseecho "$ip failed"fisleep 3}# for循环 往 fifo管道文件中写入$thread个空行for ((i=0;i<$thread;i++));doecho done >&6# 从ip.txt中读取ipwhile read ip;doread -u6#从文件描述符6中读取行(实际指向fifo管道){funcecho >&6#再次往fifo管道文件中写入一个空行} &# {} 这部分语句被放入后台作为一个子进程执行,所以不必每次等待3秒后执行#下一个,这部分的func几乎是同时完成的,当fifo中thread个空行读完后 while循环# 继续等待 read 中读取fifo数据,当后台的thread个子进程等待3秒后,按次序# 排队往fifo输入空行,这样fifo中又有了数据,while循环继续执行done < ip.txt#从ip.txt中读取数据wait#等到后台的进程都执行完毕exec 6>&-##删除文件描述符6exit 0

ip.txt中有9个ip,9个线程,调试模式执行结果

[root@localhost multhread]# ./thread.sh 9+ thread=9+ '[' 9x == x ']'+ tmp_fifofile=/tmp/12088.fifo+ mkfifo /tmp/12088.fifo+ exec+ rm /tmp/12088.fifo+ (( i=0 ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ echo+ (( i++ ))+ (( i<9 ))+ read ip+ read -u6+ func+ ping -c 3 10.217.13.1+ read ip+ read -u6+ func+ ping -c 3 10.217.13.2+ read ip+ read -u6+ func+ ping -c 3 10.217.13.3+ read ip+ read -u6+ func+ ping -c 3 10.217.13.4+ read ip+ read -u6+ func+ ping -c 3 10.217.13.5+ read ip+ read -u6+ func+ ping -c 3 10.217.13.6+ read ip+ read -u6+ func+ ping -c 3 10.217.13.7+ read ip+ read -u6+ func+ ping -c 3 10.217.13.8+ read ip+ read -u6+ func+ ping -c 3 10.217.13.9+ read ip+ wait+ r=0+ '[' 0 -eq 0 ']'+ echo '10.217.13.1 ok'10.217.13.1 ok+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.2 failed'10.217.13.2 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.3 failed'10.217.13.3 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.4 failed'10.217.13.4 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.8 failed'10.217.13.8 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.9 failed'10.217.13.9 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.5 failed'10.217.13.5 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.7 failed'10.217.13.7 failed+ sleep 3+ r=1+ '[' 1 -eq 0 ']'+ echo '10.217.13.6 failed'10.217.13.6 failed+ sleep 3+ echo+ echo+ echo+ echo+ echo+ echo+ echo+ echo+ echo+ exec+ exit 0

执行时间对比

[root@localhost multhread]# time ./thread.sh &>/dev/nullreal    0m53.051suser    0m0.004ssys     0m0.020s[root@localhost multhread]# time ./thread.sh 10 &>/dev/nullreal    0m6.024suser    0m0.013ssys     0m0.016s[root@localhost multhread]# time ./thread.sh 100 &>/dev/nullreal    0m6.027suser    0m0.015ssys     0m0.017s[root@localhost multhread]# time ./thread.sh 9 &>/dev/nullreal    0m6.023suser    0m0.013ssys     0m0.015s

可以看到线程数量正好合适时执行速度比较快。

CMDB内外网错误修正脚本多线程实现

这是工作中的一个实例,我司的IP分为内网和外网,记录在CMDB中,坑爹的CMDB不校验内外网,可以随便填,于是各种乱象,内网写成外网的,外网写成内网的,还有写“内网IP”,“公网”,还有空着啥都不写的。

CMDB中记录的格式是 :ID,对象类型,IP地址,所属机器盘点号,内外网区分,描述,可以导出为csv文件。基于一个规则文件处理导出的csv数据,找出错误的数据,并纠正,然后在导入CMDB系统。

代码实现

#!/bin/bash# Usage:# History:#thread=$1        #设置线程数,在这里所谓的线程,其实就是几乎同时放入后台(使用&)执行的进程。if [ "$1"x == ""x ] || [ "$2"x == ""x ]; then        echo "2 args: ./cmdb.sh thread cmdbfile"        exit 0fiCMDB_FILE_NAME=$2RULES_FILE=rule.txtrm -rf error correct no_rulemkdir errormkdir correctmkdir no_ruletmp_fifofile=/tmp/$.fifo                #脚本运行的当前进程ID号作为文件名mkfifo $tmp_fifofile                        #新建一个随机fifo管道文件exec 6<>$tmp_fifofile                        #定义文件描述符6指向这个fifo管道文件rm $tmp_fifofile                        #清空管道内容#定义一个函数做为线程(子进程)function func(){        id=`echo $id | sed "s/\"\",/\"kong\",/g"`        #将类型为空的替换为 kong        TYPE=`echo $id | awk -F '["]' '{print $10}'`                #cmdb中查到的网络类型,必须处理为空的类型        NET=`echo $id | awk -F '["]' '{print $6}' |awk -F '[.]' '{print $1}'`        #IP地址前8位        if [ "$NET"x = "10"x ]; then                NET=`echo $id | awk -F '["]' '{print $6}' |awk -F '[.]' '{print $1"."$2}'`        #10开头的取前16位                if [ "$NET"x = "10.62"x ] || [ "$NET"x = "10.63"x ] || [ "$NET"x = "10.64"x ]; then                        NET=`echo $id | awk -F '["]' '{print $6}' |awk -F '[.]' '{print $1"."$2"."$3}'`        #10.62/63/64开头的取前24位                fi        fi        ((++$i))        echo "$i - $NET - $TYPE"        RULE=`grep "^$NET\." $RULES_FILE | awk '{print $2}'`        #对应规则        if [ "$RULE"x = ""x ]; then                TMP=`echo $NET | awk -F '[.]' '{print $1}'`                if [ "$TMP"x = "10"x ]; then                        RULE=$TYPE                        echo $id >>no_rule/$NET.csv                else                        RULE="外网"                                #规则中没有包含的且不为私有地址的统一作为外网处理,因此公网IP规则不需要写进规则文件                fi        fi        if [ "$RULE"x != "$TYPE"x ]; then                #如果查到的类型和对应规则不符,则输出                echo $id >>error/$NET.csv                echo $id |sed "s/$TYPE/$RULE/g" >>correct/$NET.csv        fi        sleep 3}# for循环 往 fifo管道文件中写入$thread个空行for ((i=0;i<$thread;i++));do        echo done >&6#从cmdb.csv中读取i=1while read id;do        read -u6                #从文件描述符6中读取行(实际指向fifo管道)        {                func                                echo >&6        #再次往fifo管道文件中写入一个空行        } &# {} 这部分语句被放入后台作为一个子进程执行,所以不必每次等待3秒后执行#下一个,这部分的func几乎是同时完成的,当fifo中thread个空行读完后 while循环# 继续等待 read 中读取fifo数据,当后台的thread个子进程等待3秒后,按次序# 排队往fifo输入空行,这样fifo中又有了数据,while循环继续执行((i++))done < $CMDB_FILE_NAME                #从cmdb file中读取数据wait                        #等到后台的进程都执行完毕exec 6>&-                ##删除文件描述符6exit 0

优化方案

上面的代码可以解决问题,但是速度太慢了,大约要30分钟。数据总量近10万条,错误的占总数并不多,但是脚本要一条条的去检查然后比对规则。因此如果能把错误的先找出来,在用上面的脚本处理几千条错误的,速度就能快很多。

改用grep,加-v选项,能实现错误的秒级查找,然后用上面的脚本纠错,也是几秒钟的事情,整个过程不到1分钟就能完成。

#!/bin/bash# Usage:# History:#set -xif [ $# != 2 ];then        echo "args error"        exit 1firm -f error.csvtouch error.csvrm -rf tmpmkdir tmpRULEFILE="$1"CMDBFILE="$2"id=1cp $CMDBFILE tmp/tmp_$idwhile read linedo        NET=`echo $line |awk '{print $1}' |sed 's/\./\\\./g'`        RULE=`echo $line |awk '{print $2}'`        if [ "$NET"x = ""x ]; then                NET="NULLOFRULE"        fi        NET="\\\"$NET"                                RULE="\\\"$RULE\\\","                #逗号必加,处理将内网网写到描述中去的情况        grep -E ".+$NET.+" $CMDBFILE |grep -E -v ".+$NET.+$RULE.*" >> error.csv        grep -E -v "$NET" tmp/tmp_$id >tmp/tmp_file        ((id++))        mv tmp/tmp_file tmp/tmp_$iddone <$RULEFILEgrep -E "\"10\." tmp/tmp_$id >no_rules.csvgrep -E -v "\"10\." tmp/tmp_$id |grep -E -v ".+\"外网\".*" >public_error.csv

参考资料

[1]. SHELL模拟多线程脚本的详细注解.http://blog.sina.com.cn/s/blog_65d6476a01017t7f.html[2]. 管道技巧-while read line.http://blog.csdn.net/hunanchenxingyu/article/details/9998089[3]. Linux shell 实现多线程. http://llystar.iteye.com/blog/1189486

本文遵从CC版权协定,转载请以链接形式注明出处。本文链接地址: http://www.annhe.net/article-2895.html 躲在某一地点,想念一个站在来路,

Shell模拟多线程

相关文章:

你感兴趣的文章:

标签云: