次のような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
上記の回答に対するいくつかの追加は次のとおりです。
- 「&」を使用してバックグラウンドでプロセスを開始し、提案されているように対応する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 &
execは@が提案した2番目の解決策です。スチュアート・ベントレーただし、場合によっては、新しいPIDを使用してプロセスを作成する必要がある場合、または使用中のコマンドで新しいPIDまたは新しいPGIDを使用してプロセスを選択して生成することはできません(-lを使用しているrunuserの場合)。オプション)。
1)と2)の代替は、特定のPIDをターゲットにするのではなく、プロセスグループにシグナルを送信することです。
これは、子プロセスのPIDの前にマイナス記号(-)を使用してKillを使用して実行できます。
kill -TERM -$child_pid
Bashを使用すると、バックグラウンドでプロセスを開始せずにプロセスグループをターゲットにした信号をキャプチャできます。この方法は、stdinを読み取る子プロセスの能力を失うことはありません。ハンドラを使用すると、シグナルを子に渡すことができるため、子が別のプロセスグループで実行されている場合にも良いソリューションです。制限は、グループの他のメンバーもシグナルを受信することです。