1. 程式人生 > >如何在shell中處理異常

如何在shell中處理異常

前言

似乎好像大概有句話是這麼說得,好程式與壞程式之間的區別就在於它的魯棒性,也就是在異常情況下該程式是否還是在可hold住狀態,能否不死,不崩潰,或者不做出一些超出預期的事情。那要做好這些,自然而然就要學會如何去處理異常。平時寫php或者java程式等等的時候,很多人都會去注重對於異常的處理,比如try..catch等等,但往往在寫一些指令碼的時候,忽視了對於異常的判斷。本文主要就是寫如何在shell中去處理異常。因為今天太晚了,所以我就先寫第一部分,後面再寫第二部分。

返回值

要判斷一段程式碼是否出現了異常,一個最基本的判斷就是對他返回值的判斷。在shell中,我們往往規定0為正常,一切非0返回值則為不正常。但往往我們在寫shell指令碼的時候,忽略對於返回值的判斷。我們看一個很基本的shell程式

#!/bin/sh
cd /home/xxxx/
rm -rf *

這個指令碼的意思很簡單,就是cd到某一個目錄下,然後將該目錄下所有的內容都刪除。首先,rm這種東西出現在指令碼中,就是一個很危險的操作,而這個程式的關鍵之處還在於,並沒有對第一行shell的返回值進行任何的判斷,也就是說對於cd那行程式碼無論執行失敗與否,都會去執行下面的那段rm,試想如果在某些情況下cd那段程式碼失敗了之後,會出現多麼可怕的後果。所以,我們應該對於cd的程式碼做返回值的判斷。

#!/bin/sh
cd /home/xxxx/
if [ "$?"= "0" ]; then
   rm -rf *
else
   echo "cannot change directory" 1>&2
   exit 1
fi 

$?這個常量代表的就是上一段shell的返回值。這個我在前面一片文章裡也提到過shell中的trap和expr。這樣寫的話,就要比先前的程式安全多了,如果沒有cd到相應目錄,則不會去執行刪除操作。

當然,程式裡這樣寫是有些負責了,其實你也可以這樣寫:

if cd /home/xxxx/ ; then
    rm -rf *
else
    echo "cannot change directory" 1>&2
    exit 1

這是if的另一種用法。這樣寫,就要比剛才的好多了。不過其實這樣寫,也比較麻煩,其實你還可以這樣來寫:

cd /home/xxxx/ && rm -rf *

這個&&符大家肯定不陌生,那這樣來寫,是否就可以保證了程式的安全性了呢?下面就來講一下&&和||

&&和||

對於一個shell程式, shell1 && shell2 ,如果是用&&符連線的,那只有在shell1返回0(即正常)時,shell2才會執行,否則shell2根本就不執行,所以前面說得最後一種cd&&rm的這種做法是可行的,而且是安全的。那||呢,對於shell1||shell2,只有在shell1執行失敗時,shell2才會執行,否則shell2是不執行得。所以,我們可以這樣來寫:

cd /home/xxxx || error_exit "Cannot change directory"
rm -rf *

這樣,在cd /home/xxxx失敗時,就會進行error_exit這個函式的分支。那error_exit這個是啥函式呢。。哦,其實,這只是一個自定義的失敗處理函式而已。一個比較簡單的定義,當然,這也是處理程式異常的一個方式。因為總不能每次異常,都去手動寫個echo failed exit等等,所以有個統一的函式處理會比較方便。

function error_exit {
  echo "$1" 1>&2
  exit 1
}

cd /home/xxxx || error_exit "failed"
rm -rf *

如上,這是一個非常簡單的異常處理函式,在異常時,可以去主動呼叫error_exit,當然,你呼叫的時候,可以補充上行號資訊。比如:

cd /home/xxxx || error_exit "$LINENO: failed"

上面講得只是一些簡單的異常處理的方式,其實還有其他方式,比如我在上篇文章shell中的trap和expr講到的trap方式,其實也正是因為我上面文章寫了trap,所以才想寫這篇文章來把處理shell的異常給整理出來,當然也是為大家提個醒,在shell中,也是要處理異常,需要有這樣一個意識。今天太晚了,先睡覺了,明早還要早起T.T ,下篇文章,PART 2中,我會詳細總結下trap的捕獲異常的方式。

安。

用trap處理異常

之前在《shell中的trap和expr》裡簡單得介紹過了trap,所以這次我們就直接上程式碼,上處理異常的程式碼,這樣更加清晰明瞭,簡單直接。

先再重複下trap的使用方法吧:

trap [COMMAND] [SIGNAL]

代表trap會捕獲訊號[SIGNAL]後執行[COMMAND]

下面看段例項: #!/bin/bash trap “echo Fail unexpectedly on line \$FILENAME:\$LINENO!” ERR mkdir xxxx rm xxx

這段程式碼可以簡單得說明了trap在處理異常的應用,後面那個ERR,就是捕獲所有非0返回的shell執行,如果非0,那就是異常,就會被trap直接捕獲,而不會繼續往下執行了。在本文中的shell還會打印出檔名和行號等資訊,當然,這些你可以自由發揮了。當然,trap其實不僅僅可以做這一件事情,還有好多事情,他可以捕獲各種訊號,當然除了SIGKILL,所以,你可以使用trap做一些其他事情。

擴充套件閱讀-使用trap做工作環境的清理

什麼是工作環境的清理呢,其實就是一般我們日常shell中可能會有各種臨時檔案,那留下這些檔案總是不好的,所以你可以借用trap進行清理。我們看下下面這個shell程式

#!/bin/bash
TEMP_FILE=/tmp/printfile.txt
pr $1 > $TEMP_FILE
echo -n "Print file? [y/n]: "
read
if [ "$REPLY" = "y" ]; then
less $TEMP_FILE
fi
rm $TEMP_FILE

這段程式碼的作用其實就是把使用者一開始輸入的存到一個臨時檔案裡,然後詢問下使用者是否檢視,如果檢視就給print出來。噹噹然,使用者看得話,沒什麼問題。看完了後,程式也就完了,臨時檔案也就刪了。但關鍵是,如果程式在執行中異常終止了呢,或者使用者直接按ctrl+C給終止了,這樣,就會有一個臨時檔案留下。這個時候,你就可以去運用trap給你做些處理了。

#!/bin/bash

TEMP_FILE=/tmp/printfile.txt

trap "rm $TEMP_FILE; exit" SIGHUP SIGINT SIGTERM

pr $1 > $TEMP_FILE

echo -n "Print file? [y/n]: "
read
if [ "$REPLY" = "y" ]; then
lpr $TEMP_FILE
fi
rm $TEMP_FILE

看上面這個程式,即使使用者按下了ctrl+C ,trap也會可以捕獲到,這樣無論如何,臨時檔案都會被清理掉。

好了,就說這麼多了,沒啥技術含量,就是玩玩。