該篇部落格作為對之前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
}
在這篇部落格中,只是給出了oracle啟動指令碼中自訂函數的解釋,在下一篇部落格中將進入該指令碼的主體部分,其中主體部分的代碼將依賴於本篇部落格中給出的函數。