歡迎光臨
每天分享高質量文章

“Exit Trap” 讓你的 Bash 指令碼更穩固可靠 | Linux 中國

要做到這一點,秘訣就是 bash 提供的一個叫做 EXIT 的偽訊號,你可以 trap 它,當指令碼因為任何原因退出時,相應的命令或函式就會執行。
— Aaron Maxwell


致謝
編譯自 | http://redsymbol.net/articles/bash-exit-traps/ 
 作者 | Aaron Maxwell
 譯者 | Dot Craft (Dotcra) ? ? 共計翻譯:4 篇 貢獻時間:431 天

有個簡單實用的技巧可以讓你的 bash 指令碼更穩健 — 確保總是執行必要的收尾工作,哪怕是在發生異常的時候。要做到這一點,秘訣就是 bash 提供的一個叫做 EXIT 的偽訊號,你可以 trap[1] 它,當指令碼因為任何原因退出時,相應的命令或函式就會執行。我們來看看它是如何工作的。

基本的程式碼結構看起來像這樣:

  1. #!/bin/bash

  2. function finish {

  3.  # 你的收尾程式碼

  4. }

  5. trap finish EXIT

你可以把任何你覺得務必要執行的程式碼放在這個 finish 函式裡。一個很好的例子是:建立一個臨時目錄,事後再刪除它。

  1. #!/bin/bash

  2. scratch=$(mktemp -d -t tmp.XXXXXXXXXX)

  3. function finish {

  4.  rm -rf "$scratch"

  5. }

  6. trap finish EXIT

這樣,在你的核心程式碼中,你就可以在這個 $scratch 目錄裡下載、生成、操作中間或臨時資料了。註1[2]

  1. # 下載所有版本的 linux 核心…… 為了科學研究!

  2. for major in {1..4}; do

  3.  for minor in {0..99}; do

  4.    for patchlevel in {0..99}; do

  5.      tarball="linux-${major}-${minor}-${patchlevel}.tar.bz2"

  6.      curl -q "http://kernel.org/path/to/$tarball" -o "$scratch/$tarball" || true

  7.      if [ -f "$scratch/$tarball" ]; then

  8.        tar jxf "$scratch/$tarball"

  9.      fi

  10.    done

  11.  done

  12. done

  13. # 整合成單個檔案

  14. # 複製到標的位置

  15. cp "$scratch/frankenstein-linux.tar.bz2" "$1"

  16. # 指令碼結束, scratch 目錄自動被刪除

比較一下如果不用 trap ,你是怎麼刪除 scratch 目錄的:

  1. #!/bin/bash

  2. # 別這樣做!

  3. scratch=$(mktemp -d -t tmp.XXXXXXXXXX)

  4. # 在這裡插入你的幾十上百行程式碼

  5. # 都搞定了,退出之前把目錄刪除

  6. rm -rf "$scratch"

這有什麼問題麼?很多:

◈ 如果執行出錯導致指令碼提前退出, scratch 目錄及裡面的內容不會被刪除。這會導致資料洩漏,可能引發安全問題。

如果這個指令碼的設計初衷就是在指令碼末尾以前退出,那麼你必須手動複製貼上 rm 命令到每一個出口。

這也給維護帶來了麻煩。如果今後在指令碼某處添加了一個 exit ,你很可能就忘了加上刪除操作 -- 從而製造潛在的安全漏洞。

無論如何,服務要線上

另外一個場景: 想象一下你正在執行一些自動化系統運維任務,要臨時關閉一項服務,最後這項服務需要重啟,而且要萬無一失,即使指令碼執行出錯。那麼你可以這樣做:

  1. function finish {

  2.    # 重啟服務

  3.    sudo /etc/init.d/something start

  4. }

  5. trap finish EXIT

  6. sudo /etc/init.d/something stop

  7. # 主要任務程式碼

  8. # 指令碼結束,執行 finish 函式重啟服務

一個具體的實體:比如 Ubuntu 伺服器上執行著 MongoDB ,你要為 crond 寫一個指令碼來臨時關閉服務並做一些日常維護工作。你應該這樣寫:

  1. function finish {

  2.    # 重啟服務

  3.    sudo service mongdb start

  4. }

  5. trap finish EXIT

  6. # 關閉 mongod 服務

  7. sudo service mongdb stop

  8. # (如果 mongod 配置了 fork ,比如 replica set ,你可能需要執行 sudo killall --wait /usr/bin/mongod”)

控制開銷

有一種情況特別能體現 EXIT trap 的價值:如果你的指令碼執行過程中需要初始化一下成本高昂的資源,結束時要確保把它們釋放掉。比如你在 AWS (Amazon Web Services) 上工作,要在指令碼中建立一個映象。

(名詞解釋: 在亞馬遜雲上的執行的伺服器叫“實體[3]”。實體從亞馬遜機器映象Amazon Machine Image建立而來,通常被稱為 “AMI” 或 “映象” 。AMI 相當於某個特殊時間點的伺服器快照。)

我們可以這樣建立一個自定義的 AMI :

☉ 基於一個基準 AMI 執行一個實體(例如,啟動一個伺服器)。
☉ 在實體中手動或執行指令碼來做一些修改。
☉ 用修改後的實體建立一個映象。
☉ 如果不再需要這個實體,可以將其刪除。

最後一步相當重要。如果你的指令碼沒有把實體刪除掉,它會一直執行並計費。(到月底你的賬單讓你大跌眼鏡時,恐怕哭都來不及了!)

如果把 AMI 的建立封裝在腳本里,我們就可以利用 trap EXIT 來刪除實體了。我們還可以用上 EC2 的命令列工具:

  1. #!/bin/bash

  2. # 定義基準 AMI ID

  3. ami=$1

  4. # 儲存臨時實體的 ID

  5. instance=''

  6. # 作為 IT 人,讓我們看看 scratch 目錄的另類用法

  7. scratch=$(mktemp -d -t tmp.XXXXXXXXXX)

  8. function finish {

  9.    if [ -n "$instance" ]; then

  10.        ec2-terminate-instances "$instance"

  11.    fi

  12.    rm -rf "$scratch"

  13. }

  14. trap finish EXIT

  15. # 建立實體,將輸出(包含實體 ID )儲存到 scratch 目錄下的檔案裡

  16. ec2-run-instances "$ami" > "$scratch/run-instance"

  17. # 提取實體 ID

  18. instance=$(grep '^INSTANCE' "$scratch/run-instance" | cut -f 2)

指令碼執行到這裡,實體(EC2 伺服器)已經開始執行 註2[4]。接下來你可以做任何事情:在實體中安裝軟體,修改配置檔案等,然後為最終版本建立一個映象。實體會在指令碼結束時被刪除 -- 即使指令碼因錯誤而提前退出。(請確保實體建立成功後再執行業務程式碼。)

更多應用

這篇文章只講了些皮毛。我已經使用這個 bash 技巧很多年了,現在還能不時發現一些有趣的用法。你也可以把這個方法應用到你自己的場景中,從而提升你的 bash 指令碼的可靠性。

尾註

◈ 註1. mktemp 的選項 -t 在 Linux 上是可選的,在 OS X 上是必需的。帶上此選項可以讓你的指令碼有更好的可移植性。
◈ 註2. 如果只是為了獲取實體 ID ,我們不用建立檔案,直接寫成 instance=$(ec2-run-instances "$ami" | grep '^INSTANCE' | cut -f 2) 就可以。但把輸出寫入檔案可以記錄更多有用資訊,便於除錯 ,程式碼可讀性也更強。

作者簡介:美國加利福尼亞舊金山的作家,軟體工程師,企業家。Powerful Python[5] 的作者,他的 blog[6]


via: http://redsymbol.net/articles/bash-exit-traps/

作者:aaron maxwell[8] 譯者:Dotcra 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖