MacBook Pro設定メモ

ユーティリティ

memo

#!/bin/bash

################################################################################
#
# runsql
#
#   DESCRIPTION
#       SQL*Plus を使用してのSQLファイル実行を以下の機能により支援する。
#       (1) SQL*Plusの実行結果を評価する(ORA-エラーの発生有無確認など)。
#       (2) SQLファイルの実行開始・完了の確認を確実に行う(ログイン失敗・途中終了の検知)。
#       (3) 置換変数の定義をコマンドのオプションから行うことで汎用的なSQLファイルを作成しやすくする。
#       (4) SQLファイルの実行時間を計測する。
#
#   SYNTAX
#       runsql [-h]
#       runsql [-S|-s] {-d NAME=VALUE} [-o <logfile>] [-l <linesize>]
#              <login> @<sqlfile> {<param>}
#
#   OPTIONS AND PARAMETERS
#       -h : Usageを表示する。
#       -S : SQL*Plus を SILENT モードで実行する(デフォルト)。
#       -s : SQL*Plus を 非SILENT モードで実行する。
#       -d : SQL*Plus 実行時に DEFINE にて事前定義する置換変数名とその値を指定。
#            (1) -d オプションは複数回指定できる。
#            (2) -d NAME1=VALUE1 -d NAME2=VALUE2 は SQL*Plus 上で次の通り実行される。
#                DEFINE NAME1=VALUE1
#                DEFINE NAME2=VALUE2
#       -o : SQL*Plus 実行時の標準出力・標準エラー出力先のファイルを指定。
#            (1) デフォルトの出力先は $HOME/sqlplus.out
#            (2) 実行結果の評価に出力したファイルを使用するため、本ファイルの書き込み
#                に失敗した場合は実行結果を正しく評価できなくなる。
#       -l : SQL*Plus 起動時の LINESIZE 定義を指定。
#            (1) デフォルトは 80
#            (2) この指定幅に基づいて開始・ステップ・終了のセパレータの長さが決まる。
#
#       <login>   : 接続ユーザ/パスワード@接続記述子を指定。
#                   (1) 詳細は SQL*Plus の <login> を参照(sqlplus -H)
#                   (2) / as sysdba の指定には対応可能。
#       <sqlfile> : 実行するSQLファイルを指定。
#                   (1) SQL*Plusと異なり指定が必須。
#       <param>   : SQLファイルの引数を指定(複数指定可能)。
#       
#   RETURNED VALUES 
#       正常終了 : 0
#           以下の条件を全て満たした場合を正常終了とする。
#           (1) SQL*Plusの標準出力・標準エラー出力のファイルへの保存。
#           (2) SQL*Plusの終了コードが 0
#           (3) SQLファイルの最後まで実行された(途中でEXITしていない)。
#           (4) SQL実行時にエラーが発生していない。
#       警告終了 : 1
#           以下の条件を全て満たした場合を警告終了とする。
#           (1) SQL*Plusの標準出力・標準エラー出力のファイルへの保存。
#           (2) SQL*Plusの終了コードが 0
#           (3) SQLファイルの最後まで実行された(途中でEXITしていない)。
#           (4) SQL実行時にエラーが発生している。
#       異常終了 : 2
#           正常終了・警告終了条件に該当しない場合は全て異常終了とする。
#
#   COMMENTS
#       (1) エラーはSQL*Plusの出力から次のキーワードで検索する。
#           ORA-  : SQL実行時の問題
#           SP2-  : SQL*Plusが検知した問題
#           PLS-  : PL/SQL実行時の問題
#           O/S   : OSレベルで発生した問題
#           未定義の置換変数への値入力要求(NLS_LANGに依存):
#               American: Enter value for .*:
#               Japanese: .*に値を入力してください:
#       (2) SQLファイルの実行前に次の指定を暗黙に行っている。
#           動作を変更する場合はSQLファイルの冒頭などで変更すること。 
#           WHENEVER SQLERROR EXIT 1 ROLLBACK
#           WHENEVER OSERROR  EXIT 1 ROLLBACK
#           SET SERVEROUTPUT ON FORMAT WRAPPED
#           SET LINESIZE &LINESIZE
#           SET PAGESIZE 50000
#       (3) SQL*Plus 実行時の標準出力・標準エラー出力先のファイルはコマンド内
#           では削除しない。
#       (4) runsql で実行する SQLファイル内では次の記述でステップを明示する。
#           @&STEP <message>
#       (5) SQLの実行結果中で 'PICKUP! '(スペースまで) で開始する行は
#           PICKUP MSG としてレポートに表示される。
#
#   ENVIRONMENT
#       (1) sqlplus にパスが通っていること。
#       (2) runsql と同一ディレクトリ内に runsql.STEP.sql が配置されていること。
#
#   CHANGE LOG
#       2012/08/12 (ikeyanagi)     新規作成
#
################################################################################

################################################################################
# 設定
################################################################################
USAGE="Usage: runsql [-h] [-S|-s] {-d NAME=VALUE} [-o <logfile>] [-l <linesize>] <login> @<sqlfile> {<param>}"

LINESIZE=80
SC1='#'
SC2='*'
SQLPLUSOUTFILE=$HOME/sqlplus.out
STEPSQL=$(dirname $0)/runsql.STEP.sql
ERRORREGEX="^(ORA-|SP2-|PLS-|O/S|Enter value for .*:|.*に値を入力してください:)"
PICKUPREGEX="^PICKUP! "

EXIT_SUCCESS=0
EXIT_WARNING=1
EXIT_FAILURE=2

# STEP用のSQLが見つからない(異常)
if [ ! -r $STEPSQL ]; then
    echo "Required file $STEPSQL is NOT found."
    exit $EXIT_FAILURE
fi

################################################################################
# 引数処理
################################################################################
if [ $# -eq 0 ]; then
    echo "$USAGE" 1>&2
    exit $EXIT_FAILURE
fi

# runsql用のオプションを解析
optionSilent="-S"
optionDefine=
optionSqlplusOutfile=
optionLinesize=$LINESIZE
while getopts :Ssd:l:o:h opt; do
    case $opt in
      S) optionSilent="-S";;
      s) optionSilent="";;
      d) optionDefine="$optionDefine \n DEFINE $OPTARG";;
      l) optionLinesize="$OPTARG";;
      o) optionSqlplusOutfile="$OPTARG";;
      h) echo "$USAGE" 1>&2; exit $EXIT_FAILURE;;
      *) echo "Unrecognizable option -$OPTARG is specified. Exit..." 1>&2; exit $EXIT_FAILURE;;
    esac
done
shift $(($OPTIND - 1))

# SQL*Plus向けの引数を解析
sqlplusArgs=
sqlplusSqlfile=
sqlplusSqlfileArgs=
while [ $# -gt 0 ]; do
   if [ -n "$sqlplusSqlfile" ]; then
       sqlplusSqlfileArgs="$sqlplusSqlfileArgs $1"
   elif [[ "$1" =~ ^@ ]]; then
       sqlplusSqlfile="${1#@}"
   else
       sqlplusArgs="$sqlplusArgs $1"
   fi
   shift
done

################################################################################
# 実行準備
################################################################################
# SQLファイル指定がない/読み取れない場合は打ち切り
if [ -z "$sqlplusSqlfile" ]; then
    echo "NO SQL file is specified. Exit..." 1>&2
    exit $EXIT_FAILURE
elif [ ! -r "$sqlplusSqlfile" ]; then
    echo "Canno read from $sqlplusSqlfile" 1>&2
    exit $EXIT_FAILURE
fi

# SQL*Plus出力先決定
sqlplusOutfile=$SQLPLUSOUTFILE
[ -n "$optionSqlplusOutfile" ] && sqlplusOutfile="$optionSqlplusOutfile"

# SQL*Plus出力先への書き込みができない場合は打ち切り
if ! touch "$sqlplusOutfile" 2>/dev/null || [ ! -w "$sqlplusOutfile" ]; then
    echo "Cannot write to $sqlplusOutfile. Exit..."
    exit $EXIT_FAILURE
fi

# DEFINE
optionDefine=$(echo -e "$optionDefine")

# セパレータ生成
SL1=$(printf "%${optionLinesize}s" '' | tr ' ' "$SC1")
SL2=$(printf "%${optionLinesize}s" '' | tr ' ' "$SC2")

################################################################################
# 実行
################################################################################
startTime=$(date '+%Y/%m/%d %H:%M:%S'); startTimeEpoch=$(date '+%s')

cat << SQLPLUS | sqlplus $optionSilent $sqlplusArgs 2>&1 | tee $sqlplusOutfile
    SET SQLPROMPT ''

    -- 置換変数定義
    DEFINE STEP=$STEPSQL
    DEFINE LINESIZE=$optionLinesize
    DEFINE SC1=$SC1
    DEFINE SC2=$SC2 
    DEFINE SL1=$SL1
    DEFINE SL2=$SL2
    $optionDefine

    -- 環境設定
    WHENEVER SQLERROR EXIT 1 ROLLBACK
    WHENEVER OSERROR  EXIT 1 ROLLBACK
    SET SERVEROUTPUT ON FORMAT WRAPPED
    SET LINESIZE &LINESIZE
    SET PAGESIZE 50000

    -- 処理開始マーカ出力
    prompt
    prompt &SL1
    prompt &SC1 ADMINSCRIPTS RUNSQL STARTED
    prompt &SL1

    SET SQLPROMPT 'SQL> '
    @$sqlplusSqlfile $sqlplusSqlfileArgs
    SET SQLPROMPT ''

    -- 処理終了マーカ出力
    prompt
    prompt &SL1
    prompt &SC1 ADMINSCRIPTS RUNSQL COMPLETED
    prompt &SL1
    EXIT 0
SQLPLUS
rcs=("${PIPESTATUS[@]}")

endTime=$(date '+%Y/%m/%d %H:%M:%S'); endTimeEpoch=$(date '+%s')

################################################################################
# 実行結果の評価
################################################################################
# SQL*Plus終了コード
rcSqlplus=${rcs[1]}
# tee終了コード
rcTee=${rcs[2]}

# SQLファイルに含まれていたステップ数
sqlfileSteps=$(egrep "^@&STEP " $sqlplusSqlfile | wc -l)

# ログ解析
if [ $rcTee -ne 0 -o ! -r $sqlplusOutfile ]; then
    isCorrectlyLogged="NG (Following report maybe UNusable)"
    isStarted="(undetermined)"
    isCompleted="(undetermined)"
    executedSteps="(undetermined)"
    lastExecutedStep="(undetermined)"
    detectedErrors="(undetermined)"
    detectedErrorDetails=""
    pickedups="(none)"
else
    # SQL*Plusの出力を保存できたか
    isCorrectlyLogged="OK"

    # 正常にログインして開始したか(ADMINSCRIPTS RUNSQL STARTEDの出力)
    isStarted="NG (FAILED)"
    egrep -q "^$SC1 ADMINSCRIPTS RUNSQL STARTED" $sqlplusOutfile && isStarted="OK (STARTED)"

    # 最後まで処理を完了したか(ADMINSCRIPTS RUNSQL COMPLETEDの出力)
    isCompleted="NG (INCOMPLETE)"
    egrep -q "^$SC1 ADMINSCRIPTS RUNSQL COMPLETED" $sqlplusOutfile && isCompleted="OK (COMPLETED)"

    # 実行できたステップ数
    executedSteps=$(egrep "^$SC2 STEP: " $sqlplusOutfile | wc -l)

    # 最後に実行していたステップ
    lastExecutedStep=$(egrep "^$SC2 STEP: " $sqlplusOutfile | tail -n 1 | cut -c 9-)
    [ -z "$lastExecutedStep" ] && lastExecutedStep="(none)"

    # 検知されたエラー数
    detectedErrors=$(egrep "$ERRORREGEX" $sqlplusOutfile | wc -l)
    if [ $detectedErrors -eq 0 ]; then
        detectedErrorDetails="(NO ERRORS)"
    else
        detectedErrorDetails=$(echo; egrep "$ERRORREGEX" $sqlplusOutfile | sort | uniq -c | sed 's/^/  /')
    fi

    pickedups="(none)"
    if egrep -q "$PICKUPREGEX" $sqlplusOutfile; then
        pickedups=$(echo; egrep "$PICKUPREGEX" $sqlplusOutfile | sed "s/$PICKUPREGEX/      /")
    fi
fi

# runsqlの終了コード決定
if [ $rcSqlplus -eq 0 -a $rcTee -eq 0 -a "$isCompleted" = "OK (COMPLETED)" ]; then
    if [ $detectedErrors -eq 0 ]; then
        rcRunsql=$EXIT_SUCCESS; rcRunsqlDesc=Success
    else
        rcRunsql=$EXIT_WARNING; rcRunsqlDesc=Warning
    fi
else
    rcRunsql=$EXIT_FAILURE; rcRunsqlDesc=ERROR
fi

################################################################################
# レポート出力
################################################################################
sqlplusSqlfileArgsPrintable="$(echo $sqlplusSqlfileArgs)"
[ -z "$sqlplusSqlfileArgsPrintable" ] && sqlplusSqlfileArgsPrintable="(none)"


cat <<EOR | tee -a $sqlplusOutfile 1>&2



********************************************************************************
********************************************************************************
RUNSQL REPORTS
  SQLFILE    : $sqlplusSqlfile
  ARGS       : $sqlplusSqlfileArgsPrintable
  NLS_LANG   : $NLS_LANG
  OUTPUT     : $sqlplusOutfile

  SQL*Plus   : $rcSqlplus
  TIME STATS : $startTime - $endTime ($((endTimeEpoch - startTimeEpoch)) sec)
  LOGGING    : $isCorrectlyLogged
  STARTED    : $isStarted
  COMPLETED  : $isCompleted
      EXECUTED/PLANNED STEPS : $executedSteps / $sqlfileSteps
      LAST EXECUTED STEP     : $lastExecutedStep
  ERRORS     : $detectedErrors $detectedErrorDetails

  PICKUP MSG : $pickedups

  EXIT CODE  : $rcRunsql ($rcRunsqlDesc)
********************************************************************************
EOR

exit $rcRunsql
SET ECHO OFF
SET HEADING OFF
CLEAR COLUMNS
PROMPT 
PROMPT &SL2
PROMPT &SC2 STEP: &1
PROMPT &SL2
HOST date '+%Y/%m/%d %H:%M:%S'