最近在研究服务器自动部署脚本,同时也学习一下 bash 命令的运用。现在遇到并解决了一个问题,场景是这样的:
想通过 bash 脚本自动从 coding 上下载更新脚本,更新脚本里可以从 coding 的 docker 库里拉打包好的 docker,但服务器上拉之前,要先删除原来的容器和镜像,本来是通过以下代理完成的:
1 docker kill $(docker ps -a -q) 2 docker rm $(docker ps -a -q) 3 docker rmi $(docker images -a -q)
这三句是把所有的容器和镜像全部删掉,后来通过 docker 加了 Portainer 来管理 docker 后,运行代码时并不想把 portainer 的容器及镜像也删掉,因此做了以下修改:
docker kill $( docker ps -a -q | grep $(docker ps -f "name=portainer-test" -q)) docker rm $( docker ps -a -q | grep $(docker ps -f "name=portainer-test" -q)) docker rmi $( docker images -q | grep $(docker images portainer/portainer -q))
在操作的时候,把portainer 的容器及镜像排除掉。
后来,随着业务需求的增加,在我们的内部服务器上也准备通过这个脚本来更新,但内部服务器上还有个jenkins镜像,又对脚本进行了修改:
1 docker kill $( docker ps -a -q | grep -E '$(docker ps -f "name=portainer-test" -q)|$(docker ps -f "name=zealous_mestorf" -q)') 2 docker rm $( docker ps -a -q | grep -E '$(docker ps -f "name=portainer-test" -q)|$(docker ps -f "name=zealous_mestorf" -q)') 3 docker rmi $( docker images -q | grep -E '$(docker images portainer/portainer -q)|$(docker images jenkinsci/blueocean -q)')
先找出 portainer 和 jenkins 的容器和镜像,排除后再进行删除。
看着这段代码,我本身十分地抗拒。这段代码十分的不优雅,完全是硬编码。虽然现在是可以用,但是如果之后还要多排除一个镜像呢?还要再加一次吗?
后来想想,多一事不如少一事,能工作的代码就是好代码。这个脚本在内部服务器上运行成功了,之后就是把这个脚本同步到云服务器上,然后报错了。。。因为云服务器上只有 portainer 没有 jenkins。。。
结果还是不能偷懒,经过近1小时的努力,终于现在修改成了现在这样:
1 # join array to string. ('a' 'b') => 'a,b' 2 join() { 3 arr=($@) 4 ids=${arr[*]} 5 echo ${ids// /|} 6 } 7 8 # init ignore list 9 ignore_containers_list=('portainer-test' 'zealous_mestorf') 10 ignore_images_list=('portainer/portainer' 'jenkinsci/blueocean') 11 ignore_docker_containers_list=() 12 ignore_docker_images_list=() 13 14 # get containerID 15 for ((loop_i=0; loop_i<${#ignore_containers_list[*]}; loop_i++)) 16 do 17 ignore_docker_containers_list[$loop_i]=$(docker ps -f "name=${ignore_containers_list[$loop_i]}" -q) 18 done 19 20 # get imageID 21 for ((loop_i=0; loop_i<${#ignore_images_list[*]}; loop_i++)) 22 do 23 ignore_docker_images_list[$loop_i]=$(docker images ${ignore_images_list[$loop_i]} -q) 24 done 25 26 # get ignore id string 27 ignore_container_string=`join ${ignore_docker_containers_list[*]}` 28 ignore_image_string=`join ${ignore_docker_images_list[*]}` 29 30 # run command 31 echo "docker ps -a -q | grep -E '[^$ignore_container_string]' | xargs docker kill" | sh 32 33 echo "docker ps -a -q | grep -E '[^$ignore_container_string]' | xargs docker rm" | sh 34 35 echo "docker images -q | grep -E '[^$ignore_image_string]' | xargs docker rmi" | sh
先是一个 join 函数,相当于 javascript 里的 join,就是把数组变成字符串,现在分隔符只有『 | 』,之后还要改成能传分隔符。
然后是定义container 和 image 列表,由于 container 和 image 的名称各不相同,因此分为两个列表,之后有新的 docker 要排除,直接把名称放进去就行。
再次就是从上面两个列表中把操作的 ID 提取出来,做成两个 ID 列表,然后把 ID 列表通过 join 函数变成 a|b|c 的形式,方便之后 command 调用。
command 这里做了两处调整:
第一处是grep -E:
名称映射 ID 这一步已经上面完成了,因此只要排除这些 ID 即可,这里通过 grep 的正则 -E 来实现,通过[^a|b|c]来排除需要排除的 ID,那么剩下的就是可以删除的容器及镜像了。
第二处是xargs
本来是通过$()来做子语句查询,后来调试的时候怎么都出不来,以及之前就想把命令改成 xargs 模式来做,看着更清楚,于是就统一改成了 xargs 来做。xargs 的功能是把之前的生成结果当成参数传给后面的命令,这样就不会有$()的回调地狱了。
内部服务器,通过。
云服务器,通过。
搞定。