Linux中國

「Exit Trap」 讓你的 Bash 腳本更穩固可靠

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

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

#!/bin/bash
function finish {
  # 你的收尾代碼
}
trap finish EXIT

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

#!/bin/bash
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
function finish {
  rm -rf "$scratch"
}
trap finish EXIT

這樣,在你的核心代碼中,你就可以在這個 $scratch 目錄里下載、生成、操作中間或臨時數據了。 注1

# 下載所有版本的 linux 內核…… 為了科學研究!
for major in {1..4}; do
  for minor in {0..99}; do
    for patchlevel in {0..99}; do
      tarball="linux-${major}-${minor}-${patchlevel}.tar.bz2"
      curl -q "http://kernel.org/path/to/$tarball" -o "$scratch/$tarball" || true
      if [ -f "$scratch/$tarball" ]; then
        tar jxf "$scratch/$tarball"
      fi
    done
  done
done
# 整合成單個文件
# 複製到目標位置
cp "$scratch/frankenstein-linux.tar.bz2" "$1"
# 腳本結束, scratch 目錄自動被刪除

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

#!/bin/bash
# 別這樣做!

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

# 在這裡插入你的幾十上百行代碼

# 都搞定了,退出之前把目錄刪除
rm -rf "$scratch"

這有什麼問題么?很多:

  • 如果運行出錯導致腳本提前退出, scratch 目錄及裡面的內容不會被刪除。這會導致資料泄漏,可能引發安全問題。
  • 如果這個腳本的設計初衷就是在腳本末尾以前退出,那麼你必須手動複製粘貼 rm 命令到每一個出口。
  • 這也給維護帶來了麻煩。如果今後在腳本某處添加了一個 exit ,你很可能就忘了加上刪除操作 -- 從而製造潛在的安全漏洞。

無論如何,服務要在線

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

function finish {
    # 重啟服務
    sudo /etc/init.d/something start
}
trap finish EXIT
sudo /etc/init.d/something stop
# 主要任務代碼

# 腳本結束,執行 finish 函數重啟服務

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

function finish {
    # 重啟服務
    sudo service mongdb start
}
trap finish EXIT
# 關閉 mongod 服務
sudo service mongdb stop
# (如果 mongod 配置了 fork ,比如 replica set ,你可能需要執行 「sudo killall --wait /usr/bin/mongod」)

控制開銷

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

(名詞解釋: 在亞馬遜雲上的運行的伺服器叫「實例」。實例從 亞馬遜機器鏡像 Amazon Machine Image 創建而來,通常被稱為 「AMI」 或 「鏡像」 。AMI 相當於某個特殊時間點的伺服器快照。)

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

  1. 基於一個基準 AMI 運行一個實例(例如,啟動一個伺服器)。
  2. 在實例中手動或運行腳本來做一些修改。
  3. 用修改後的實例創建一個鏡像。
  4. 如果不再需要這個實例,可以將其刪除。

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

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

#!/bin/bash
# 定義基準 AMI 的 ID
ami=$1
# 保存臨時實例的 ID
instance=''
# 作為 IT 人,讓我們看看 scratch 目錄的另類用法
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
function finish {
    if [ -n "$instance" ]; then
        ec2-terminate-instances "$instance"
    fi
    rm -rf "$scratch"
}
trap finish EXIT
# 創建實例,將輸出(包含實例 ID )保存到 scratch 目錄下的文件里
ec2-run-instances "$ami" > "$scratch/run-instance"
# 提取實例 ID
instance=$(grep '^INSTANCE' "$scratch/run-instance" | cut -f 2)

腳本執行到這裡,實例(EC2 伺服器)已經開始運行 注2 。接下來你可以做任何事情:在實例中安裝軟體,修改配置文件等,然後為最終版本創建一個鏡像。實例會在腳本結束時被刪除 -- 即使腳本因錯誤而提前退出。(請確保實例創建成功後再運行業務代碼。)

更多應用

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

章節附註

  • 注1. mktemp 的選項 -t 在 Linux 上是可選的,在 OS X 上是必需的。帶上此選項可以讓你的腳本有更好的可移植性。
  • 注2. 如果只是為了獲取實例 ID ,我們不用創建文件,直接寫成 instance=$(ec2-run-instances "$ami" | grep '^INSTANCE' | cut -f 2) 就可以。但把輸出寫入文件可以記錄更多有用信息,便於調試 ,代碼可讀性也更強。

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

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

作者:aaron maxwell 譯者:Dotcra 校對:wxy

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


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國