シグナルを無視するプログラムを Ctrl + C で強制終了することは可能ですか?

シグナルを無視するプログラムを Ctrl + C で強制終了することは可能ですか?

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_programCtrlc

関連事実:

  • あるプロセスを別のプロセスと並行して実行する標準的な方法は、そのうちの 1 つをバックグラウンドで非同期的に実行することです (つまり、 を終了して&)。
  • Ctrl+を押すとc、ターミナル エミュレーターがフォアグラウンド プロセス グループのプロセスに SIGINT を送信します。
  • 一部の (単純な) シェルは、すべてを同じプロセス グループで実行します。コマンドをバックグラウンドで実行するように指示された場合、コマンドが/dev/null入力を盗むのを防ぐために、その stdin を または同等のファイルにリダイレクトします。
  • 他のシェルは、各コマンドを別のプロセスグループで実行できます。コマンドをバックグラウンドで実行するように指示された場合、そのコマンドのstdinはそのまま残ります。それでも、バックグラウンドのコマンドは、制御端末からの入力を盗むことはできません。SIGTTINこれにより、シェルは、新しいフォアグラウンド プロセス グループを端末に通知することで、ジョブをバックグラウンドからフォアグラウンドに移動できます。このメカニズムはジョブ制御と呼ばれ、無効にすることができます。スクリプトでは、デフォルトで無効になっています。
  • いずれにしても、バックグラウンドのプロセスは端末から読み取ることができません。つまり、my_programstdin が端末であり、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 ^Csleep 1doneCtrlc

コメントより:

SIGINT を無視するプログラムは通常、非常に正当な理由があってそうします。

本当です。SIGKILL の代わりに SIGTERM または SIGHUP を試してください。問題のプログラムは、これらのうち少なくとも 1 つを無視せず、適切なクリーンアップを行って正常に終了する可能性があります。

関連情報