Ctrl + Cでキャンセルするプロセスを制御する


13

Linuxで起動し、小さなBashスクリプトを実行するライブCDがあります。スクリプトは、2番目のプログラム(通常はコンパイル済みのC ++バイナリ)を検索して実行します。

Ctrl+ を押すと、2番目のプログラムを中止できますC。何をすべき起こることは、第2のプログラムを停止していることである、とBashスクリプトがクリーンアップを実行し続けます。どのような実際に起こることは、メインアプリケーションとBashスクリプトの両方が終了していることです。これは問題です。

そこで、trap組み込みを使用してBashにSIGINTを無視するように指示しました。そして今Ctrl+ CC ++アプリケーションを終了しますが、Bashは実行を続けます。すごい。

そうそう...時々、「2番目のアプリケーション」は別のBashスクリプトです。そして、中にいる場合、Ctrl+ C今行わない全く何も

明らかに、このようなものがどのように機能するかについての私の理解は間違っています...ユーザーがCtrl+を押したときにどのプロセスがSIGINTを取得するかを制御するにはどうすればよいCですか?私はこの信号を1つの特定のプロセスに向けたいです。

回答:


11

インターネットの顔を何時間も検索した後、私は答えを見つけました。

  1. Linuxにはプロセスグループの概念があります

  2. TTYドライバーには、フォアグラウンドプロセスグループの概念があります。

  3. Ctrl+ を押すCと、TTY はフォアグラウンドプロセスグループのすべてのプロセスに送信SIGINTします。(このブログエントリも参照してください。)

これが、コンパイルされたバイナリそれを起動するスクリプトの両方が破壊される理由です。実際、メインアプリケーションにはこの信号のみを受け取り、起動スクリプト受け取りません

これで解決策が明らかになりました。アプリケーションを新しいプロセスグループに配置し、このTTYのフォアグラウンドプロセスグループにする必要があります。どうやらそれを行うコマンドは

setsid -c <applcation>

そして、それがすべてです。これで、ユーザーがCtrl+を押すとC、SIGINTがアプリケーション(およびその子を持つ可能性がある)に送信され、他のユーザーは送信されません。それは私が欲しかったものです。

  • setsid それ自体は、アプリケーションを新しいプロセスグループ(実際には、プロセスグループのグループである新しい「セッション」全体)に配置します。

  • -cフラグを追加すると、この新しいプロセスグループが現在のTTYの「フォアグラウンド」プロセスグループになります。(つまり、+ SIGINTを押すと取得されます)CtrlC

Bashが新しいプロセスグループでプロセスを実行するか実行しないかについて、多くの矛盾する情報を見てきました。(特に、「対話型」シェルと「非対話型」シェルでは異なるように見えます。)これを巧妙なパイプトリックで動作させることができるかもしれないという提案を見てきました...わかりません。しかし、上記のアプローチは私にはうまくいくようです。


5
スクリプトを実行すると、ジョブ制御はデフォルトで無効になりますが、で有効にできますset -msetsid子を実行するたびに使用するよりも少しクリーンでシンプルです。
psusi

@psusiヒントをありがとう!私は一人の子供を実行する必要があるだけなので、大したことではありません。しかし、今ではBashマニュアルのどこを見るべきかがわかりました...
MathematicalOrchid 14年

皮肉なことに、親にsigintをキャッチさせたいという逆の問題がありますが、それは「賢いパイプの策略」のためではありません。プログレスグループ->プロセスグループ
アンドリュードマゼク

2

f01のコメントで述べたように、子プロセスにSIGTERMを送信する必要があります。以下に、^ Cをトラップして子プロセスにシグナルを送信する方法を示すスクリプトをいくつか示します。

まず、親。

トラップテスト

#!/bin/bash

# trap test
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
child=sleeploop

set_trap()
{
    sig=$1
    msg="echo -e \"\n$myname received ^C, sending $sig to $child, $pid\""
    trap "$msg; kill -s $sig $pid" SIGINT
}
trap "echo \"bye from $myname\"" EXIT

echo "running $child..."
./$child 5  &
pid=$!

# set_trap SIGINT
set_trap SIGTERM
echo "$child pid = $pid"

wait $pid
echo "$myname finished waiting"

そして今、子供。

スリープループ

#!/bin/bash

# child script for traptest
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
delay="$1"

set_trap()
{
    sig=$1
    trap "echo -e '\n$myname received $sig signal';exit 0" $sig
}

trap "echo \"bye from $myname\"" EXIT
set_trap SIGTERM
set_trap SIGINT

#Select sleep mode
if false
then
    echo "Using foreground sleep"
    Sleep()
    {
        sleep $delay
    }
else
    echo "Using background sleep"
    Sleep()
    {
        sleep "$delay" &
        wait $!
    }
fi

#Time to snooze :)
for ((i=0; i<5; i++));
do
    echo "$i: sleeping for $delay"
    Sleep
done

echo "$myname terminated normally"

traptestがSIGTERMを送信する場合、物事はうまく動作しますが、traptestがSIGINTを送信する場合、sleeploopはそれを認識しません。

sleeploopがSIGTERMをトラップし、スリープモードがフォアグラウンドの場合、現在のスリープから復帰するまでシグナルに応答できません。ただし、スリープモードがバックグラウンドの場合、すぐに応答します。


この偉大な例をありがとう、それは:)私は私のスクリプトをどのように改善できるかを理解して私をたくさん助けた
TabeaKischka

2

開始bashスクリプト。

  • 2番目のプログラムのPIDを追跡する

  • SIGINTをキャッチ

  • SIGINTをキャッチしたら、SIGINTを2番目のプログラムPIDに送信します


1
それは役に立たないかもしれません。親スクリプトでSIGINTをトラップすると、親から送信したか別のシェルから送信したかにかかわらず、子スクリプトはSIGINTを受信できなくなります。しかし、それは思ったほど悪くはありません。なぜなら、SIGTERMを子に送信できるからです。
PM 2Ring 14年

1
SIGTERMが「優先」されるのはなぜですか?
MathematicalOrchid 14年

それらはほとんど似ています。SIGINTは、Ctrl + Cなどの制御端末/ユーザーによって送信される信号です。SIGTERMは、プロセスを終了する場合にも送信できるものです。ここでより多くの考えen.wikipedia.org/wiki/Unix_signal
f01 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.