シグナルの処理
「シグナル」というのをご存知ですか?
長い事unix関連のOSを触っていらっしゃる方なら当たり前の様に知っていると思います。
ここでは、簡単に「シグナル」についての説明をした後、前回の「関数応用」の続きを説明したいと思います。
「シグナル」というのは、一言で言ってしまえば、プロセスに送る信号です。
暴走したプロセスや再起動したいプロセスがあった時などによく使われる「kill」コマンドによってプロセスに発信します。受け取ったプロセスは、その「シグナル」に応じた動作をする訳です。
よく、コマンドを打ってから、間違えた事に気付き、「Ctrl+C」を押して終了させたりする事ありませんか?
あれもシグナルを送信しています。
ちなみに、「Ctrl+C」は以下のコマンドでも同じ意味を持ちます。
kill -2 プロセスID
プロセスを強制終了させたい時は以下のコマンドです。
kill -9 プロセスID
デーモンプロセスを再起動させたい時は以下のコマンドです。
kill -1 プロセスID
この様に、「kill」コマンドの引数によって、送るシグナルを選ぶ事ができます。
私が使っているCentOS5.3に入っているGNUのkillコマンドでは、以下の様な種類があります。
# kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX #
これだけの量がありますが、一般的に重要だと言われてるのは「0,1,2,3,9,15」ぐらいです。
でも、「0」というのは上の表には無いですよね?
この「0」というシグナルは、プロセス自身が自分に送るシグナルになります。
bashスクリプトで言うところの「exit」コマンドが「0」というシグナルに当たります。
「exit」コマンドを使うと、そのスクリプトが動いているプロセスはシグナル「0」を受け取り、処理を終了します。
ただ、混同しないで欲しいのは、「シグナル」は「終了コード」とは違います。
「exit 1」とやっても、シグナルは「0」です。まったく別物ですので気をつけてください。
さて、bashスクリプトにも、この「シグナル」の対策をしておく必要があります。
基本的に、スクリプトの中に処理の流れを書いてますが、例外中の例外の処理がこの「シグナル処理」に当たります。
スクリプト実行中に、強制的に終了させられると処理中のファイルなどの「ゴミ」が残る可能性があるからです。
ちゃんとスクリプトが終了する時には、終了処理をしなければならないという事です。
やっと前回の「関数応用」の話に戻ります。
#!/bin/bash
## 引数処理サンプル2
## 関数定義
function F_Usage()
{
echo "USAGE : `basename $0` [-abc][-d STRING]"
return 0
}
if [ "$1" = "" ]
then
echo "引数がありません"
F_Usage
exit 1
fi
while getopts ":abc:d" V_OPT
do
case ${V_OPT} in
a)
echo "引数はaです"
;;
b)
echo "引数はbです"
;;
c)
echo "引数はcです"
echo "引数に付加された文字は${OPTARG}です"
;;
d)
echo "引数はdです"
;;
*)
echo "想定していない引数です"
F_Usage
exit 1
;;
esac
done
shift $(( ${OPTIND} -1 ))
if [ "$1" != "" ]
then
echo "$1は想定していない引数です"
F_Usage
exit 1
fi
exit 0
前回は「なぜexitコマンドを関数化しないのか」についてで話が終わってましたね。
ここまでの話を読んできた方なら判ると思いますが、「終了処理」も関数化する必要があるからです。
正常終了の時、異常終了の時、そしてシグナルを受け取った時、全ての終了形態に対して
終了処理を記述してやる必要があるのです。
同様の処理を行う訳ですから、当然関数化した方が効率がよくなると言う事ですね。
では、終了処理を関数化してみましょう。
function F_Exit()
{
local V_EXIT=$1
exit ${V_EXIT}
}
引数を受け取って、それを終了コードとして「exit」コマンドを実行する関数です。
この関数を組み込んでみましょう。
#!/bin/bash
## 引数処理サンプル2
## 関数定義
function F_Usage()
{
echo "USAGE : `basename $0` [-abc][-d STRING]"
return 0
}
function F_Exit()
{
V_EXIT=$1
exit ${V_EXIT}
}
## メイン処理
if [ "$1" = "" ]
then
echo "引数がありません"
F_Usage
F_Exit 1
fi
while getopts ":abc:d" V_OPT
do
case ${V_OPT} in
a)
echo "引数はaです"
;;
b)
echo "引数はbです"
;;
c)
echo "引数はcです"
echo "引数に付加された文字は${OPTARG}です"
;;
d)
echo "引数はdです"
;;
*)
echo "想定していない引数です"
F_Usage
F_Exit 1
;;
esac
done
shift $(( ${OPTIND} -1 ))
if [ "$1" != "" ]
then
echo "$1は想定していない引数です"
F_Usage
F_Exit 1
fi
F_Exit 0
これで正常でも異常でも、スクリプト終了時には「F_Exit」関数を通って終了する事になりました。
次は「シグナル」処理への対処です。
まず、スクリプト側でシグナルを受け取れる様にしておく必要があります。
シグナルを受け取れる様にするためには「trap」コマンドをスクリプトの前部で実行しておく必要があります。
trap '受信時に実行するコマンド' 受け取るシグナル
という形で書く必要があります。
ここでは、「1,2,3,9,15」に対して、「F_Exit」関数に引数1を付け実行させます。
シグナルを受け取ったら問答無用で「異常終了」させるという訳です。
trap 'F_Exit 1' 1 2 3 9 15
これをスクリプトに追加してみます。
#!/bin/bash
## 引数処理サンプル2
## 関数定義
function F_Usage()
{
echo "USAGE : `basename $0` [-abc][-d STRING]"
return 0
}
function F_Exit()
{
V_EXIT=$1
exit ${V_EXIT}
}
## シグナル処理
trap 'echo "シグナル受信"
F_Exit 1' 1 2 3 9 15
## メイン処理
if [ "$1" = "" ]
then
echo "引数がありません"
F_Usage
F_Exit 1
fi
while getopts ":abc:d" V_OPT
do
case ${V_OPT} in
a)
echo "引数はaです"
;;
b)
echo "引数はbです"
;;
c)
echo "引数はcです"
echo "引数に付加された文字は${OPTARG}です"
;;
d)
echo "引数はdです"
;;
*)
echo "想定していない引数です"
F_Usage
F_Exit 1
;;
esac
done
shift $(( ${OPTIND} -1 ))
if [ "$1" != "" ]
then
echo "$1は想定していない引数です"
F_Usage
F_Exit 1
fi
sleep 60
F_Exit 0
シグナル検証用にちょっと細工をしてみました。
引数処理を正常に通過すると、自分のPIDを出力して60秒間スリープします。
その時に「Ctrl+C」を押してスクリプトを終了させてみましょう。
# ./option07.sh 引数がありません USAGE : option07.sh [-abc][-d STRING] # echo $? 1 # ./option07.sh -e 想定していない引数です USAGE : option07.sh [-abc][-d STRING] # echo $? 1 # ./option07.sh -a bbb 引数はaです bbbは想定していない引数です USAGE : option07.sh [-abc][-d STRING] # echo $? 1 # ./option07.sh -a 引数はaです 7213 シグナル受信 # echo $? 1 #
「Ctrl+C」を押した時には、ちゃんと「シグナル受信」と出力されましたでしょうか?
ここでは「1,2,3,9,15」のシグナルに対して全て同じ様な処理をする様に設定されてますが、
もちろん個別に設定する事もできます。
その際は「trap」コマンドで個々のシグナルに対して定義してやるだけです。
この「引数処理」「USAGE出力」「シグナル処理」は全てのスクリプトに共通して必要になってくる物だと思います。
実践編では、ここで作ったスクリプトをテンプレートにしてスクリプトを作成していきたいと思います。
