1. 程式人生 > 實用技巧 >bash指令碼關於單引號和變數的問題

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 的功能是把之前的生成結果當成引數傳給後面的命令,這樣就不會有$()的回撥地獄了。

內部伺服器,通過。

雲伺服器,通過。

搞定。