bash脚本关于单引号和变量的问题

最近在研究服务器自动部署脚本,同时也学习一下 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 的功能是把之前的生成结果当成参数传给后面的命令,这样就不会有$()的回调地狱了。

 

内部服务器,通过。

云服务器,通过。

搞定。

posted @ 2020-10-28 13:31  shiningsun  阅读(427)  评论(0编辑  收藏  举报