シグナルの処理

「シグナル」というのをご存知ですか?
長い事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出力」「シグナル処理」は全てのスクリプトに共通して必要になってくる物だと思います。

実践編では、ここで作ったスクリプトをテンプレートにしてスクリプトを作成していきたいと思います。