(轉) Linux Shell經典實例解析


原文:http://blog.csdn.net/yonggeit/article/details/72779955

該篇博客作為對之前Linux Shell常用技巧和高級技巧系列博客的總結,將以Oracle數據庫服務器啟動腳本為例,逐行進行解釋和說明,以幫助我們能夠更好的學習和理解Shell腳本的慣用技巧和強大之處。
      Oracle的啟動腳本從功能上講主要分為兩個部分,第一部分是初始化各種環境變量,以確認當前Oracle服務器的版本,從而進一步確定啟動當前服務器的步驟和具體需要使用的各種Oracle工具,第二部分是基於之前判斷的結果,讀取當前服務器的各種配置信息,之后再通過Oracle提供的Shell命令完成數據庫的啟動工作。

LOGMSG="logger -puser.alert -s "
#1. 信號捕捉,當腳本捕捉到信號SIGHUP(1)、SIGINT(2)和SIGQUIT(3)時,執行exit命令退出腳本。
trap 'exit' 1 2 3
#2. 如果當前Shell環境中指定ORACLE_TRACE變量的值為T,則通過執行set -x命令來啟動腳本的跟蹤功能。
case $ORACLE_TRACE in
    T) set -x ;;
esac
SAVE_PATH=/bin:/usr/bin:/etc:${PATH} ; export PATH
SAVE_LLP=$LD_LIBRARY_PATH
#3. $1,即當前腳本的第一個參數,通過查看init.d目錄下調用該腳本的Shell腳本oracle,可以獲悉該參數的值為$ORACLE_HOME環境變量的值。
ORACLE_HOME_LISTNER=$1
#4. 如果該值不存在,則給出錯誤提示信息,以及該腳本的合法使用方式。
if [ ! $ORACLE_HOME_LISTNER ] ; then
    echo "ORACLE_HOME_LISTNER is not SET, unable to auto-start Oracle Net Listener"
    echo "Usage: $0 ORACLE_HOME"
else
    LOG=$ORACLE_HOME_LISTNER/listener.log
    #5. 導出ORACLE_HOME環境變量的值,由於使用了export命令,該變量的值在子Shell中將同樣有效。
    export ORACLE_HOME=$ORACLE_HOME_LISTNER
    #6. 判斷$ORACLE_HOME_LISTNER/bin/tnslsnr文件是否有可執行權限,如果為真,則通過該命令啟動Oracle監聽,需要注意的是,由於在該行命令的末尾有一個&符號,這表示該命令將在后台執行。
    #7. 在啟動監聽時,將標准輸出以追加的方式重定向到$LOG變量指向的文件,同時也將標准錯誤輸出也執行到該文件。
    if [ -x $ORACLE_HOME_LISTNER/bin/tnslsnr ] ; then
        echo "$0: Starting Oracle Net Listener" >> $LOG 2>&1
        $ORACLE_HOME_LISTNER/bin/lsnrctl start >> $LOG 2>&1 &
        #8. 通過提取lsnrctl version的返回信息獲取當前Oracle服務器的版本,該命令的返回結果為:
        #    LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 14-DEC-2011 17:23:12
        #
        #    Copyright (c) 1991, 2009, Oracle.  All rights reserved.
        #
        #    Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)))
        #    TNSLSNR for Linux: Version 11.2.0.1.0 - Production
        #        TNS for Linux: Version 11.2.0.1.0 - Production
        #        Unix Domain Socket IPC NT Protocol Adaptor for Linux: Version 11.2.0.1.0 - Production
        #        Oracle Bequeath NT Protocol Adapter for Linux: Version 11.2.0.1.0 - Production
        #        TCP    /IP NT Protocol Adapter for Linux: Version 11.2.0.1.0 - Production,,
        #    The command completed successfully
        #9. 在通過grep命令對以上結果進行過濾,只輸出包含"LSNRCTL for"的行,其結果為:
        #    LSNRCTL for Linux: Version 11.2.0.1.0 - Production on 14-DEC-2011 17:25:21
        #10.通過cut命令對以上結果進行拆分,分隔符為-d選項指定的空格字符,-f5表示將輸出拆分后的第五個字段,其結果為:
        #    11.2.0.1.0
        #11.通過cut命令對以上結果進行二次拆分,但是這次的分隔符改為點(.),本次獲取的字段為第一個字段,即11。    
        VER10LIST=`$ORACLE_HOME_LISTNER/bin/lsnrctl version | grep "LSNRCTL for " | cut -d' ' -f5 | cut -d'.' -f1`
        export VER10LIST
    else
        echo "Failed to auto-start Oracle Net Listener using $ORACLE_HOME_LISTNER/bin/tnslsnr"
    fi
fi

ORATAB=/etc/oratab
#12.我想此處代碼的本意應為判斷/etc/oratab文件是否以文件的形式存在,然而下面的寫法將會使if判斷永遠為真,因此應改為if [ ! -f $ORATAB ]; then。-f用於判斷其后的變量是否是為普通文件。如果該文件不存在,腳本將直接退出,退出值為1,表示失敗。需要說明的是,在Linux中,通用的規則是返回0表示執行成功。
if [ ! $ORATAB ] ; then
    echo "$ORATAB not found"
    exit 1;
fi

#13. checkversionmismatch是該腳本的自定義函數,用於判斷客戶端工具sqlplus和Oracle服務器之間的版本是否匹配。
checkversionmismatch() {
    if [ $VER10LIST ] ; then
        #14. 通過sqlplus -V獲取sqlplus的版本,再該通過grep命令過濾,僅輸出包含Release的行,其結果為:
        #    SQL*Plus: Release 11.2.0.1.0 Production
        #15. 基於以上結果,再通過兩次cut命令的拆分,最后輸出:11。這里cut的作用已經在上面的注釋中給出。
        VER10INST=`sqlplus -V | grep "Release " | cut -d' ' -f3 | cut -d'.' -f1`
        #16. 如果服務器的版本($VER10LIST)小於sqlplus的版本(VER10INST),將輸出不匹配的提示信息。這里-lt用於比較數值型變量,表示A 小於 B。
        if [ $VER10LIST -lt $VER10INST ] ; then
            $LOGMSG "Listener version $VER10LIST NOT supported with Database version $VER10INST"
            $LOGMSG "Restart Oracle Net Listener using an alternate ORACLE_HOME_LISTNER:"
            $LOGMSG "lsnrctl start"
        fi
    fi
}

startinst() {
    export ORACLE_SID
    #17. 將oracle的bin目錄放置到PATH環境變量中,已便於之后的直接調用。
    PATH=$ORACLE_HOME/bin:${SAVE_PATH} ; export PATH
    #18. LD_LIBRARY_PATH指出so文件所在的路徑,這里將oracle所依賴的lib的路徑賦值給該變量,以便oracle執行程序在啟動時可以找到他們。
    LD_LIBRARY_PATH=${ORACLE_HOME}/lib:${SAVE_LLP} ; export LD_LIBRARY_PATH
    #19. 下面的變量是oracle啟動時所需要的服務器實例初始化文件。
    PFILE=${ORACLE_HOME}/dbs/init${ORACLE_SID}.ora
    SPFILE=${ORACLE_HOME}/dbs/spfile${ORACLE_SID}.ora
    SPFILE1=${ORACLE_HOME}/dbs/spfile.ora
    
    echo ""
    echo "$0: Starting up database \"$ORACLE_SID\""
    date
    echo ""
    checkversionmismatch
    
    #20. 下面的代碼邏輯用於區分當前服務器的版本是否為V6或V7,因為后面的啟動邏輯需要為這兩個版本做特殊處理。
    #21. 首先判斷$ORACLE_HOME/bin/sqldba是否以普通文件的形式存在,如果存在,將通過sqldba命令獲取版本信息。
    VERSION=undef
    if [ -f $ORACLE_HOME/bin/sqldba ] ; then
        SQLDBA=sqldba
        VERSION=`$ORACLE_HOME/bin/sqldba command=exit | awk '
            /SQL\*DBA: (Release|Version)/ {split($3, V, ".") ;
            print V[1]}'`
        #22. 如果版本為6,則什么也不用做,否則將VERSION變量的值統一為internal。
        case $VERSION in
            "6") ;;
            *) VERSION="internal"
        esac
    else
        #23. 再次判斷$ORACLE_HOME/bin/svrmgrl是否以普通文件的形式存在,如果存在,SQLDBA的命令將為svrmgrl,版本為internal,否則SQLDBA命令將指向sqlplus。需要說明的是,不管是這里的svrmgrl還是上面的sqldba,都是為了向以前版本的兼容,才用SQLDBA來動態的表示他們,事實上,在我們后來的版本中,基本都是使用sqlplus。
        if [ -f $ORACLE_HOME/bin/svrmgrl ] ; then
            SQLDBA=svrmgrl
            VERSION="internal"
        else
            SQLDBA="sqlplus /nolog"
        fi
    fi    
    #24. 變量STATUS為1時表示正常值,其它值均表示oracle的進程已經拉起。
    #25. 先是判斷$ORACLE_HOME/dbs/sgadef${ORACLE_SID}.dbf和$ORACLE_HOME/dbs/sgadef${ORACLE_SID}.ora這兩個文件是否已經存在。其中${ORACLE_SID}表示變量,shell在執行時會使用該變量的實際值予以替換,這里之所有用花括號括起${ORACLE_SID},而不是直接使用$ORACLE_SID,是因為如果這樣使用的話,shell腳本會將$ORACLE_SID.ora視為一個變量。
    STATUS=1
    if [ -f $ORACLE_HOME/dbs/sgadef${ORACLE_SID}.dbf ] ; then
        STATUS="-1"
    fi
    if [ -f $ORACLE_HOME/dbs/sgadef${ORACLE_SID}.ora ] ; then
        STATUS="-1"
    fi
    #26. pmon是oracle的進程監控進程,是oracle服務器的核心進程之一。這里通過ps命令輸出當前linux服務器所有進程的列表,再通過grep命令進行過濾,其中-w選擇表示全詞匹配,最后再通過一個grep命令過濾掉上一個grep命令,這里的-v表示取反,即不包含grep的行。
    pmon=`ps -ef | grep -w "ora_pmon_$ORACLE_SID"  | grep -v grep`
    if [ "$pmon" != "" ] ; then
        STATUS="-1"
        $LOGMSG "Warning: ${INST} \"${ORACLE_SID}\" already started."
    fi
    #27. 這里是判斷數值型變量$STATUS是否為-1,即進程已經啟動。
    if [ $STATUS -eq -1 ] ; then
        $LOGMSG "Warning: ${INST} \"${ORACLE_SID}\" possibly left running when system went down (system crash?)."
        $LOGMSG "Action: Notify Database Administrator."
        #28. 既然oracle服務器實例已經啟動,這里就需要根據oracle的版本,用不同的工具和關閉語法shutdown已經啟動的實例。
        case $VERSION in
            "6")  sqldba "command=shutdown abort" ;;
            "internal")  $SQLDBA $args <<EOF
        connect internal
        shutdown abort
        EOF
            ;;
            *)  $SQLDBA $args <<EOF
        connect / as sysdba
        shutdown abort
        quit
        EOF
            ;;
        esac
        #29. $?是shell腳本的內置變量,用於判斷上面關閉oracle服務器實例的操作是否成功,0表示成功,其他值均表示失敗。
        if [ $? -eq 0 ] ; then
            STATUS=1
        else
            $LOGMSG "Error: ${INST} \"${ORACLE_SID}\" NOT started."
        fi
    fi
    if [ $STATUS -eq 1 ] ; then
        #30. 判斷$SPFILE、$SPFILE1或$PFILE是否存在,-e表示其后面的變量表示的文件是否存在,-o表示這幾個條件時間的或關系,即C語言中的||。
        #31. 根本oracle的版本,用不同的oracle工具啟動oracle服務器實例,其中不同的工具所使用的語法也不同,這里我們主要需要關注的是sqlplus。
        #32. 在通過oracle工具啟動服務器時,這里使用了shell中的HERE DOCUMENT,這樣可以將一批命令一次性傳遞給sqlplus這樣的oracle命令。
        if [ -e $SPFILE -o -e $SPFILE1 -o -e $PFILE ] ; then
            case $VERSION in
                "6")  sqldba command=startup ;;
                "internal") $SQLDBA <<EOF
            connect internal
            startup
            EOF
                ;;
                *) $SQLDBA <<EOF
            connect / as sysdba
            startup
            quit
            EOF
                ;;
            esac
            #33. 通過判斷以上命令的返回值,來判斷是否啟動成功。
            if [ $? -eq 0 ] ; then
                echo ""
                echo "$0: ${INST} \"${ORACLE_SID}\" warm started."
            else
                $LOGMSG ""
                $LOGMSG "Error: ${INST} \"${ORACLE_SID}\" NOT started."
            fi
        else
            $LOGMSG ""
            $LOGMSG "No init file found for ${INST} \"${ORACLE_SID}\"."
            $LOGMSG "Error: ${INST} \"${ORACLE_SID}\" NOT started."
        fi
    fi
}

#34. 用於啟動oracle的AMS實例的函數。
startasminst() {
    export ORACLE_SID
    #34. $LINE的值在后面的調用中會給出,該值源自oratab文件的輸出,其內容為:MyOrcl:/opt/oracle/product/OraHome:Y
    #35. 這里使用awk命令提取第二個域字段,其中冒號(:)為各個域之間的分隔符,第二個變量($2)為當前實例的oracle主目錄。
    ORACLE_HOME=`echo $LINE | awk -F: '{print $2}' -`
    export ORACLE_HOME
    
    #36. 判斷$ORACLE_HOME/bin/crsctl是否有執行權限。
    if [ ! -x $ORACLE_HOME/bin/crsctl ]; then
        $LOGMSG "$ORACLE_HOME/bin/crsctl not found when attempting to start"
        $LOGMSG "  ASM instance $ORACLE_SID."
    else
        #37. 反復執行$ORACLE_HOME/bin/crsctl命令,直到其執行成功,或在執行15次失敗后退出腳本。
        COUNT=0
        $ORACLE_HOME/bin/crsctl check css
        RC=$?
        #38. 判斷crsctl命令是否執行成功,如果不等於表示執行失敗,則繼續執行。
        while [ "$RC" != "0" ]; do
            #39. 通過expr命令,將COUNT的變量值加一,這里也可以使用let命令,如((COUNT=COUNT+1))。
            COUNT=`expr $COUNT + 1`
            if [ $COUNT = 15 ] ; then
                # 15 tries with 20 sec interval => 5 minutes timeout
                $LOGMSG "Timed out waiting to start ASM instance $ORACLE_SID"
                $LOGMSG "  CSS service is NOT available."
                exit $COUNT
            fi
            $LOGMSG "Waiting for Oracle CSS service to be available before starting "
            $LOGMSG " ASM instance $ORACLE_SID. Wait $COUNT."
            #40. 每次執行之間都休眠20秒。
            sleep 20
            $ORACLE_HOME/bin/crsctl check css
            RC=$?
        done
    fi
    #41. asm在啟動成功后,調用startinst函數啟動該實例。
    startinst

}

 

經過2個多月的努力,該系列博文到這里已經即將結束,希望該系列的文章能夠給諸位今后的工作帶來些許幫助,也希望能有機會與大家在技術上多多交流,互相取長補短,同時也敬請大家能夠繼續關注我在后面給出的關於其他技術主題的系列博文。最后在這里感謝諸位網友的支持。
      言歸正傳,該篇博客將承接上一篇博客,進入oracle啟動腳本的主體邏輯部分。

#1. /etc/oratab腳本的格式如下:
#    MyOrcl1:/opt/oracle/product/OraHome:Y
#    MyOrcl2:/opt/oracle/product/OraHome:N
#該文件的開頭處有很多的注釋說明,都是以#開頭,這些注釋需要在后面的處理中被忽略。在有用部分中,每行表示一個oracle實例,在同一行中,包含3個字段,他們之間用#冒號分隔,第一個字段為oracle的sid,第二個字段為oracle實例的主目錄,最后一個字段表示本次啟動是否拉起該實例,如果為Y則拉起,N則忽略。
#2. cat以管道的形式,將每行的都輸出給while循環,作為其輸入並賦值給LINE變量,如果到了$ORATAB文件的末尾,while循環將退出。
cat $ORATAB | while read LINE; do
    #3. 如果當前行以#開頭后面跟隨任意字符,則為注釋說明,直接忽略即可。
    #4. 如果合法的數據行,用awk命令進行切分,並提取第一個域字段,即oracle的sid值,賦值給變量ORACLE_SID。如果該變量為空,則直接忽略,continue命令將回到循環的開頭處。
    case $LINE in
        \#*)  ;;
        *) 
        ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
        if [ "$ORACLE_SID" = '*' ] ; then
            ORACLE_SID=""
            continue
        fi
        #5. 這里提取$LINE變量的最后一個字段,其中NF表示awk的輸入行的字段數量,在本例中NF的值為3,$LINE的第三個域為狀態字段,只有當該值為Y時才拉起該sid。
        if [ "`echo $LINE | awk -F: '{print $NF}' -`" = "Y" ] ; then
            #6. 通過cut命令截取ORACLE_SID的第一個字符,如果其值為加號(+),則視其為asm instance。
            #7. 這里的cut命令可以替換為${ORACLE_SID:0:1},0表示從變量$ORACLE_SID的第一個字符開始,取1個字符。
            if [ `echo $ORACLE_SID | cut -b 1` = '+' ]; then
                INST="ASM instance"
                ORACLE_HOME=`echo $LINE | awk -F: '{print $2}' -`
                export ORACLE_HOME
                LOG=$ORACLE_HOME/startup.log
                #8. 通過touch命令創建一個日志文件,同時賦予讀權限。
                touch $LOG
                chmod a+r $LOG
                echo "Processing $INST \"$ORACLE_SID\": log file $ORACLE_HOME/startup.log"
                #9. 調用啟動asm實例的函數,並將標准輸出重定向到剛剛創建的日志文件,同時也將標准錯誤輸出也重定向到該文件。
                startasminst >> $LOG 2>&1
            fi
        fi
        ;;
    esac
done

#10. 如果執行之上的操作失敗,則直接退出腳本,退出值為2。
if [ "$?" != "0" ] ; then
    exit 2
fi
#11. 該部分將重新遍歷/etc/oratab文件,並啟動數據庫實例。該段邏輯中的shell技巧和上面的邏輯基本相同,這里僅給出差異部分。
cat $ORATAB | while read LINE; do
    case $LINE in
        \#*) ;;
        *) 
        ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
        if [ "$ORACLE_SID" = '*' ] ; then
            ORACLE_SID=""
            continue
        fi
        # Proceed only if last field is 'Y'.
        if [ "`echo $LINE | awk -F: '{print $NF}' -`" = "Y" ] ; then
            #12. 這里和上面不同是,是判斷ORACLE_SID的第一個字符不為加號(+),這表示該實例為正常的數據庫實例。
            if [ `echo $ORACLE_SID | cut -b 1` != '+' ]; then
                INST="Database instance"
                ORACLE_HOME=`echo $LINE | awk -F: '{print $2}' -`
                export ORACLE_HOME
                LOG=$ORACLE_HOME/startup.log
                touch $LOG
                chmod a+r $LOG
                echo "Processing $INST \"$ORACLE_SID\": log file $ORACLE_HOME/startup.log"
                startinst >> $LOG 2>&1
            fi
        fi
        ;;
    esac
done

#13. 該段代碼邏輯的shell應用技巧和之前幾段的基本雷同,這里我只是給出技巧上的差異部分。
cat $ORATAB | while read LINE;do
    case $LINE in
        \#*) ;;
        *)
        ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
        if [ "$ORACLE_SID" = '*' ] ; then
            ORACLE_SID=""
            continue
        fi
        if [ "`echo $LINE | awk -F: '{print $NF}' -`" = "W" ] ; then
            W_ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
            cat $ORATAB | while read LINE; do
                case $LINE in
                    \#*) ;;
                    *)
                    ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
                    if [ "$ORACLE_SID" = '*' ] ; then
                        ORACLE_SID=""
                        continue
                    fi
                    if [ `echo $ORACLE_SID | cut -b 1` = '+' ]; then
                        INST="ASM instance"
                        ORACLE_HOME=`echo $LINE | awk -F: '{print $2}' -`
                        if [ -x $ORACLE_HOME/bin/srvctl ] ; then
                            COUNT=0
                            NODE=`olsnodes -l`
                            #14. 執行下面的命令,並將其結果用grep命令過濾,只保留包含$ORACLE_SID is running的行,這里$ORACLE_SID將完成變量替換。
                            RNODE=`srvctl status asm -n $NODE | grep "$ORACLE_SID is running"`
                            RC=$?
                            #15. 如果執行失敗,將繼續執行。
                            while [ "$RC" != "0" ]; do
                                #16. COUNT=$((COUNT+1))是另外一種進行數值型變量計算的表示方式。
                                COUNT=$((COUNT+1))
                                #17. -eq表示等於$COUNT等於5。
                                if [ $COUNT -eq 5 ] ; then
                                    $LOGMSG "Error: Timed out waiting on CRS to start ASM instance $ORACLE_SID"
                                    exit $COUNT
                                fi
                                $LOGMSG "Waiting for Oracle CRS service to start ASM instance $ORACLE_SID"
                                $LOGMSG "Wait $COUNT."
                                sleep 60
                                RNODE=`srvctl status asm -n $NODE | grep "$ORACLE_SID is running"`
                                RC=$?
                            done
                        else
                            $LOGMSG "Error: \"${W_ORACLE_SID}\" has dependency on ASM instance \"${ORACLE_SID}\""
                            $LOGMSG "Error: Need $ORACLE_HOME/bin/srvctl to check this dependency"
                        fi
                    fi 
                    ;;
                esac
            done # innner while
        fi
        ;;
    esac
done # outer while

#18. 在該段代碼邏輯中,主要是用於處理/etc/oratab文件中最后一個字段的值為W的情況,它表示所有的asm實例均已啟動完畢,進入等待狀態,此時將只能啟動數據庫實例。從Shell的應用技巧視角看,該段邏輯和之前的shell技巧沒有太多差別,這里就不再一一給出注釋說明了。
cat $ORATAB | while read LINE; do
    case $LINE in
        \#*) ;;
        *)
        ORACLE_SID=`echo $LINE | awk -F: '{print $1}' -`
        if [ "$ORACLE_SID" = '*' ] ; then
            ORACLE_SID=""
            continue
        fi
        if [ "`echo $LINE | awk -F: '{print $NF}' -`" = "W" ] ; then
            INST="Database instance"
            if [ `echo $ORACLE_SID | cut -b 1` = '+' ]; then
                $LOGMSG "Error: ${INST} \"${ORACLE_SID}\" NOT started"
                $LOGMSG "Error: incorrect usage: 'W' not allowed for ASM instances"
                continue
            fi
            ORACLE_HOME=`echo $LINE | awk -F: '{print $2}' -`
            export ORACLE_HOME
            LOG=$ORACLE_HOME/startup.log
            touch $LOG
            chmod a+r $LOG
            echo "Processing $INST \"$ORACLE_SID\": log file $ORACLE_HOME/startup.log"
            startinst >> $LOG 2>&1
        fi
        ;;
    esac
done
      最后需要說明的是,有興趣的讀者可以繼續自行研究$ORACLE_HOME/bin目錄下的另外一個Shell腳本(dbshut),該腳本主要用於關閉Oracle數據庫服務器,其代碼結構和Shell技巧與該腳本(dbstart)極為相似。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM