BashのサブプロセスにSIGTERMを渡す

BashのサブプロセスにSIGTERMを渡す

次のようなBashスクリプトがあります。

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

スクリプトを実行しているbashシェルがSIGTERMシグナルを受け取ると、実行中のサーバーにSIGTERMを送信する必要があります(これによりトラップは不可能になります)。それは可能ですか?

答え1

努力する:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

通常、bash子プロセスの実行中はすべてのシグナルは無視されます。としてサーバーを起動すると、&シェルジョブ制御システムの背景に配置され、$!サーバーのPID(およびと共に使用されるwait)が保存されますkill。その後、呼び出しは、wait指定されたPID(サーバー)を持つジョブが完了するのを待ちます。または放出される信号

シェルがそれを受信するとSIGTERM(またはサーバーが独立して終了したとき)、呼び出しが返されますwait(サーバーの終了コードで終了するか、信号が受信された場合は信号番号+ 128で終了します)。その後、シェルがSIGTERMを受信すると、_term終了する前にSIGTERMトラップハンドラで指定された関数を呼び出します(ここではクリーンアップを実行し、シグナルをサーバープロセスに手動で伝播しますkill)。

答え2

Bashは、SIGTERMなどの信号を現在待機しているプロセスに転送しません。スクリプトを終了したい場合続けて入力してくださいサーバー(サーバーを直接起動したかのようにシグナルなどを処理できるようにする)を使用する必要がありますexecシェルを開きたいプロセスに置き換えます。:

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

何らかの理由でシェルを保存する必要がある場合(たとえば、サーバーのシャットダウン後にいくつかのクリーンアップ操作が必要な場合)、およびを組み合わせて使用​​する必要がtrapありwaitますkill。バラよりセンサースミスの答え

答え3

アンドレアス・ウェソンは次のように指摘しています。OPの例のように呼び出しから戻る必要がない場合は、コマンドを介して呼び出すだけでexec十分です(@Stuart P. Bentleyの返信)。そうでなければ、「伝統的な」trap 'kill $CHILDPID' TERM(@cuonglmの答え)が始まりますが、waitトラップハンドラが実行された後に呼び出しが実際に返されます。したがって、「追加」呼び出しを行うことをお勧めしますwait@user1463361の返信)。

これは改善されたが、依然として競争条件を生成する。すなわち、信号送信者がTERM信号送信を再試行しない限り、プロセスは終了しない可能性がある。脆弱性ウィンドウは、トラップハンドラの登録とサブプロセスのPID履歴の間にあります。

以下は脆弱性を排除します(再利用のために関数にパッケージ化されています)。

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid} 2>/dev/null
    trap - TERM INT
    wait ${term_child_pid} 2>/dev/null
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term

答え4

上記の回答に対するいくつかの追加は次のとおりです。

  1. 「&」を使用してバックグラウンドでプロセスを開始し、提案されているように対応するPIDを待ちます。おじさん子プロセスの実行中にハンドラを実行できますが、子プロセスが切り離されるとすぐにstdinが閉じられるため、子プロセスは入力をキャプチャする機能を失います。 stdinを開いたままにするには、子プロセスにパイプされる無限ループを追加するだけです。バラよりこの投稿

次に、現在のシェルから入力を読み取り、それを子プロセスのprocファイルに書き込み、標準入力に入力します。

(while true; do sleep 10000; done) | /bin/start/main/server --nodaemon &
child_pid=$!
while :
do
    result=$(kill -0 $mypid > /dev/null 2>&1)
    if [ $? -ne 0 ] ; then
        # process is gone
        break
    else
        # read input in the current shell and store it in a variable. The timeout only works with Bash, not with Bourne-Shell. You will need to find a way to read stdin instead and sleep 1 sec between each loop
        read -t 1 input 
        
        # echo the input to the proc file of the runuser process so it goes to its stdin
        echo $input > /proc/$child_pid/fd/0 2>/dev/null 
    fi
done
wait $child_pid

注:これはLinuxではうまく機能しますが、他のUnixプラットフォームでは少し調整が必要な場合があります。

編集する:より簡単なアプローチは、標準入力をファイル記述子にコピーして、バックグラウンドプロセスの標準入力として使用できることです。

exec 3<&0
/bin/start/main/server --nodaemon <&3 &
  1. execは@が提案した2番目の解決策です。スチュアート・ベントレーただし、場合によっては、新しいPIDを使用してプロセスを作成する必要がある場合、または使用中のコマンドで新しいPIDまたは新しいPGIDを使用してプロセスを選択して生成することはできません(-lを使用しているrunuserの場合)。オプション)。

  2. 1)と2)の代替は、特定のPIDをターゲットにするのではなく、プロセスグループにシグナルを送信することです。

これは、子プロセスのPIDの前にマイナス記号(-)を使用してKillを使用して実行できます。

kill -TERM -$child_pid

Bashを使用すると、バックグラウンドでプロセスを開始せずにプロセスグループをターゲットにした信号をキャプチャできます。この方法は、stdinを読み取る子プロセスの能力を失うことはありません。ハンドラを使用すると、シグナルを子に渡すことができるため、子が別のプロセスグループで実行されている場合にも良いソリューションです。制限は、グループの他のメンバーもシグナルを受信することです。

関連情報