
SIGINT を無視するプログラムがありますが、これをフォアグラウンドで実行したいと考えています。Ctrl-C で強制的に終了する方法を見つけたいと思います。./wrapper.sh my_program
無視された SIGINT を検出して SIGKILL を生成することで、不正な動作をしているプログラムを強制的に終了させるラッパー ( と呼ぶ) を作成する方法はありますか?
この答えこれは私が探しているものとは正反対です。シグナルを無視するプログラムを SIGINT で強制的に終了させたいのです。
答え1
SIGINT を SIGKILL に「変換」するラッパーを構築しましょう。
my_program
実行され、Ctrl+で SIGINT を受け取るプロセスが必要ですc。SIGINT を受信すると、 に SIGKILL を送信する必要があります。 +によって実際に SIGINT を受信するmy_program
必要はありません。追加のプロセスによって SIGKILL が受信されれば十分です。my_program
Ctrlc
関連事実:
- あるプロセスを別のプロセスと並行して実行する標準的な方法は、そのうちの 1 つをバックグラウンドで非同期的に実行することです (つまり、 を終了して
&
)。 - Ctrl+を押すとc、ターミナル エミュレーターがフォアグラウンド プロセス グループのプロセスに SIGINT を送信します。
- 一部の (単純な) シェルは、すべてを同じプロセス グループで実行します。コマンドをバックグラウンドで実行するように指示された場合、コマンドが
/dev/null
入力を盗むのを防ぐために、その stdin を または同等のファイルにリダイレクトします。 - 他のシェルは、各コマンドを別のプロセスグループで実行できます。コマンドをバックグラウンドで実行するように指示された場合、そのコマンドのstdinはそのまま残ります。それでも、バックグラウンドのコマンドは、制御端末からの入力を盗むことはできません。
SIGTTIN
これにより、シェルは、新しいフォアグラウンド プロセス グループを端末に通知することで、ジョブをバックグラウンドからフォアグラウンドに移動できます。このメカニズムはジョブ制御と呼ばれ、無効にすることができます。スクリプトでは、デフォルトで無効になっています。 - いずれにしても、バックグラウンドのプロセスは端末から読み取ることができません。つまり、
my_program
stdin が端末であり、my_program
そこから読み取る必要がある場合は、バックグラウンドで実行すべきではありません。 - 残念ながら、一部のシェルでは、バックグラウンドで他のプロセスも実行すべきではありません。一部のシェルは、ジョブ制御が無効になっている場合でも、別のプロセス グループを使用します。フォアグラウンド プロセス グループにない他のプロセスは、Ctrl+時に SIGINT を受信しないcため、それを SIGKILL に「変換」することはできません。
- 私の Debian 10 には、
posh
すべてを同じプロセス グループ内で実行するシェルがあります。
これにより、次のラッパーが生成されます。
#!/usr/bin/env posh
( trap 'kill -s KILL 0' INT
while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"
exec "$@"
my_program
は(または指定したもの) を実行します。引数exec
my_program
を付けることもできます。 はラッパーを置き換えます。 stdin (端末である可能性があります) から読み取ることができるだけでなく、その PID は置き換えられたラッパーの 1 つになります。 この意味で、ラッパーは透過的です。 さらに、これは優れた機能を備えています。 の PID は開始my_program
前にわかっているmy_program
ので、他のプロセス (この場合はバックグラウンドのサブシェル) で簡単に使用して、 がmy_program
実際に終了したことを検出できます。
はtrap
、SIGINT を SIGKILL に「変換」します。 は、kill -s KILL 0
プロセス グループ全体に SIGKILL を送信します。my_program
グループ内に の子プロセスがある場合は、その子プロセスも含みます (子プロセスmy_program
だけを強制終了する場合は、代わりに を使用しますkill -s KILL "$$"
)。ただし、kill -s 0 "$$"
の存在my_program
のみをテストします。
すべてを同じプロセス グループで実行するシェルを必要としない代替手段があります。秘訣は、パイプライン内のプロセスは 1 つのプロセス グループで実行する必要があることです。巧妙なリダイレクトにより、各部分が互いに接続されていないパイプラインを構築できます。
#!/bin/sh -
exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
trap 'kill -s KILL 0' INT
while :; do sleep 1; done
)
このバリアントでは、my_program
ラッパーは置き換えられません。 2 番目kill
は、SIGINT を SIGKILL に「変換」することです。 1 番目は、ループが存続する状況で終了するkill
場合にループを終了することです。my_program
期待どおりに動作しないシナリオがいくつかあります (どのラッパーでも)。その中には次のようなものがあります:
- フォークして終了する場合、実際のジョブは子プロセスに任せます。問題となっているプログラムは、 +しよ
my_program
うとしたため、おそらくこのように動作しません。ただし、一般的にはそうなる場合があります。Ctrlc my_program
別のプロセス グループで子プロセスを生成し、その子プロセスを親と一緒に終了したい場合。- は、
my_program
端末がCtrl+に対して SIGINT を送信しないように設定します。これが発生すると思われる場合は、との間に をc配置してラッパーを改善します。この場合、プログラムは SIGINT を無視しない可能性があり、端末から SIGINT を取得しないようにするだけになる可能性があります。したがって、SIGKILL はやりすぎかもしれません。端末のこの機能を復元するだけで十分であり、+ が機能し始める可能性があります。stty -F /dev/tty intr ^C
sleep 1
done
Ctrlc
コメントより:
SIGINT を無視するプログラムは通常、非常に正当な理由があってそうします。
本当です。SIGKILL の代わりに SIGTERM または SIGHUP を試してください。問題のプログラムは、これらのうち少なくとも 1 つを無視せず、適切なクリーンアップを行って正常に終了する可能性があります。