Shell 黑科技之匿名函数实现任务并行化
shell 作为一门系统级别胶水语言,学习成本低,用起来很方便,但是缺点也显而易见:性能问题一直为人锁诟病。所以 shell 也就多用在简单的系统管理等场合,数据处理等等要求比较高的场合一般会选择 java、Python 等功能更强大、性能更好的语言。 最近用shell写了一个小函数,用来在集群间批量执行命令并返回结果: for ip in ips do ssh work@$ip "echo 1; exit" 2>/dev/null done 执行下来功能没啥问题,但是性能却一塌糊涂,6台机器执行将近 5s,因为这个 for 循环 ssh 的过程是串行的。 那咱们有没有优化的方案呢? 首先想到的是不依赖任何三方工具或库(实际上我们 RD 也没有权限安装),有没有比较方便的办法。当然有了,每个 ssh 起来放后台不就行了吗? 嗯,说干就干,撸起袖子立马上: ssh work@$ip "echo 1; exit" > a.txt 2>/dev/null & ... wait 其实就是最后加了个 & 放后台执行,嗯执行起来速度确实快了,但是。。。会出现副作用,会显示类似任务后台执行信息: work@zz_console 20:24:39 ~ > echo 1 & [1] 13013 1 work@zz_console 20:24:44 ~ > [1]+ Done echo 1 work@zz_console 20:24:44 ~ > 重定向都无济于事。怎么办?为了消除这些信息,自然又想到了子进程 () : Jun@VAIO 10.252.182.238 19:48:28 ~ > (echo 1 &) 1 Jun@VAIO 10.252.182.238 20:28:13 ~ > 提示信息看起来完美解决了,但是新的问题又出来了:无法用 wait 等待后台进程执行完毕之后主进程再继续执行。这个问题怎么解决呢? 从 superuser 上的答案来看,又提到了新的思路: set +m: +m Job control is closed. 但是实际试了下也不行,只能隐去最后一条 Done 的完成信息,初始的信息并不会隐去: Jun@VAIO 10.252.182.238 20:37:02 ~ > set +m Jun@VAIO 10.252.182.238 20:39:52 ~ > echo 1 & [1] 3148 Jun@VAIO 10.252.182.238 20:39:57 ~ > 1 Jun@VAIO 10.252.182.238 20:39:58 ~ > 最后 stackoverflow 有人给了个不错的思路:用函数即可解决,因为当前后台任务的提示信息只会在当前shell显示,而函数 {} 创建了子shell/bash,所以不会在当前shell显示提示信息。 function a { echo "I'm background task $1" sleep 5 } function b { for i in {1..10}; do a $i & done wait } 2>/dev/null $ b I'm background task 1 I'm background task 3 I'm background task 2 I'm background task 4 I'm background task 6 I'm background task 7 I'm background task 5 I'm background task 9 I'm background task 8 I'm background task 10 #And there's a delay of 5 seconds before I get my prompt back. 不过我实际试了下,仅用函数其实并不能完美的解决上述后台等待和副作用的问题,我这里最终用{} 做匿名函数创建子shell的方式完美的解决了这个问题,让提示信息不在当前shell 显示,并且能用wait等待。最终代码如下: zzcmd(){ start_time=`date +%s` c=0 [[ $1 == "" || $2 == "" ]] && cmd_usage && return ips="`echo "$1"|grep -Po '((d{1,3}.){3}d{1,3})'`" [[ $ips == "" ]] && ips="$(eval echo "$$1")" [[ $ips == "" ]] && ips="`cat $1`" set +m nanoseconds=`date +%N` for ip in $ips do [[ $ip == "" || $ip == " " || ! $ip =~ ^[0-9]+.[0-9]+.[0-9]+.[0-9]+$ ]] && redEcho "$ip ip invalid..." && continue { timeout 15 ssh work@$ip "echo -e "e[41;37;1m ------------------- ${ip} -------------------e[0mn";$2; exit" > ${nanoseconds}_$ip & } 2>/dev/null ((c++)) done wait cat ${nanoseconds}_* rm -f ${nanoseconds}_* set -m end_time=`date +%s` cost_time=$(($end_time-$start_time)) #cost_time_pretty=`date -d@$cost_time +%M"min"%S` greenEcho "******************* 本次执行 $c 台机器,耗时 ${cost_time}s *******************" } 总结: 解决问题的关键在于 {} 和 () 的区别,外加 set +m:
后记: 当然了也有很多第三方的工具和库也可以解决这个问题,比如Ansible、puppet 等自动化运维管理工具,还有GNU的paralle程序等,但都没有这个方便和易于理解。 Refer:[1]Running bash commands in the background without printing job and process ids https://stackoverflow.com/questions/7686989/running-bash-commands-in-the-background-without-printing-job-and-process-ids [2]Preventing bash from displaying “Done” when a background command finishes executing https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut [3]Bash脚本实现批量作业并行化 http://bit.ly/2qZ8CZV [4]GNU Parallel指南 http://www.52php.cn/article/p-hfnjtnps-bog.html [5]GNU parallel http://about.uuspider.com/2015/09/22/parallel.html [6]GNU Parallel http://www.52php.cn/blog/huozhanfeng/article/p-3006344.html [7]如何利用多核CPU来加速你的Linux命令 — awk,sed,bzip2,grep,wc等 http://www.vaikan.com/use-multiple-cpu-cores-with-your-linux-commands/ [8]Bash脚本15分钟进阶教程 http://www.vaikan.com/bash-scripting/ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |