キーストロークでBashスクリプトから呼び出されたタイムアウトを強制終了できないのはなぜですか?


11

[編集:これは、生成されたすべてのプロセスを強制終了する方法を尋ねる他のいくつかの質問に似ています。答えはすべてpkillを使用することのようです。したがって、私の質問の核心は次のようになるかもしれません。スクリプトによって生成されたすべてのプロセスにCtrl-C / Zを伝播する方法はありますか?]

coreutils(ここで説明)からrectimeoutコマンドでSoX を呼び出す場合、Bashスクリプト内から呼び出された後、キーストロークでSoX を強制終了する方法はないようです。

例:

timeout 10 rec test.wav

... bashからCtrl+ CまたはCtrl+を使用Zして強制終了できますが、スクリプト内から呼び出された場合は強制終了できません。

timeout 10 ping nowhere

...で殺さすることができますCtrl+ CまたはCtrl+ Zのbashから、としてCtrl+ Zそれは、スクリプト内から実行していますとき。

プロセスIDを見つけてそのように強制終了することはできますが、標準のブレークキーストロークを使用できないのはなぜですか。そして、私ができるようにスクリプトを構成する方法はありますか?


2
Ctrl + Zはプロセスを強制終了せず、一時停止するだけです。bgof fgコマンドを指定すると、実行を続けます。とにかく、あなたの最初の例と3dの例の間に違いはありますか?
terdon

私はtimeout自分のシステムには持っていませんがsleep、コマンドラインで直接入力したり、ソースから取得したり、実行したり、インタプリタに明示的に渡したりしても、killは機能します
Kevin

@terdonありがとう、私は例を明確にしました。
会議

回答:


18

Ctrl+ などのシグナルキーCは、フォアグラウンドプロセスグループのすべてのプロセスにシグナルを送信します。

典型的なケースでは、プロセスグループはパイプラインです。たとえば、ではhead <somefile | sort、シェルと同様に、head実行中のプロセスと実行sort中のプロセスが同じプロセスグループに属しているため、すべてがシグナルを受信します。バックグラウンドでジョブを実行する場合(somecommand &)、そのジョブは独自のプロセスグループにあるため、Ctrl+を押しCても影響はありません。

timeoutプログラムは、独自のプロセスグループで自分自身を配置します。ソースコードから:

/* Ensure we're in our own group so all subprocesses can be killed.
   Note we don't just put the child in a separate group as
   then we would need to worry about foreground and background groups
   and propagating signals between them.  */
setpgid (0, 0);

タイムアウトが発生すると、timeoutそれがメンバーとなっているプロセスグループを強制終了するという簡単な方法を実行します。自身を別のプロセスグループに配置しているため、その親プロセスはグループに含まれません。ここでプロセスグループを使用すると、子アプリケーションが複数のプロセスに分岐した場合、そのすべてのプロセスがシグナルを受信することが保証されます。

timeoutコマンドラインで直接実行してCtrl+ を押すCと、結果のSIGINTはtimeout子プロセスと子プロセスの両方で受信されますが、timeoutの親プロセスであるインタラクティブシェルでは受信されません。場合はtimeout、スクリプトから呼び出され、スクリプトを実行している唯一のシェルは、信号を受信します。timeoutそれは別のプロセスグループでありますので、それを得ることはありません。

trap組み込みのシェルスクリプトでシグナルハンドラを設定できます。残念ながら、それはそれほど簡単ではありません。このことを考慮:

#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date

2秒後にCtrl+ を押すとC、5秒間完全に待機してから、「中断」メッセージが出力されます。これは、フォアグラウンドジョブがアクティブな間、シェルがトラップコードを実行しないためです。

これを修正するには、ジョブをバックグラウンドで実行します。シグナルハンドラーでを呼び出しkillて、シグナルをtimeoutプロセスグループにリレーします。

#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid

非常に卑劣-美しく働いた!私は今、はるかに賢いです、メルシー!
会議

13

Gillesによって提供された優れた答えに基づいて構築します。タイムアウトコマンドにはフォアグラウンドオプションがあり、使用すると、CTRL + Cでタイムアウトコマンドを終了します。

#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date

これは素晴らしい答えです-受け入れられた答えよりも単純で、timeoutGNU coreutils を使用してうまく動作しました。
RichVel

1

基本的にCtrl+ CSIGINTシグナルを送信し、Ctrl+ ZはSIGTSTPシグナルを送信します。

SIGTSTPプロセスを停止するだけで、SIGCONT続行します。

これは、コマンドラインでフォークされたフォアグラウンドプロセスで機能します。

プロセスがバックグラウンドプロセスの場合は、別の方法でそのシグナルをプロセスに送信する必要があります。killそれを行います。理論的には、そのkillの「-」演算子も子プロセスにシグナルを送る必要がありますが、これが期待どおりに機能することはめったにありません。

参考資料Unix-Signals


これは真実ですが、少なくとも「期待どおりに機能しない」まで(これは期待に依存します。プロセスグループについて読む必要があります)、まったく関係ありません。この質問は、SIGINTとSIGTSTPに関する混乱を裏切るものではありません。
Gilles 'SO-悪をやめ

0

既存のスクリプトに「検索および置換」アプローチを提供することにより、@ Gillesの回答を改善します。

  1. コードの先頭に次のスニペットを追加します。
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}
  1. 次のコードをINTハンドラーに追加(またはマージ)します。
pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            #echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
  1. スクリプトのtimeoutコマンドをmy_timeout関数に置き換えます。

次にスクリプトの例を示します。

#!/bin/bash

# see "killing timeout": /unix//a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
    local args tp ret
    args="$@"
    timeout $args &
    tp=$!
    echo "pid of timeout: $tp"
    timeout_pids+=($tp)
    wait $tp
    ret=$?
    count=${#timeout_pids[@]}
    for ((i = 0; i < count; i++)); do
        if [ "${timeout_pids[i]}" = "$tp" ] ; then
            unset 'timeout_pids[i]'
        fi
    done
    return $ret
}

cleanup(){
    echo "-----------------------------------------"
    echo "Restoring previous routing table settings"
}

pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
    echo "Executing pre-cleanup..."
    for i in "${timeout_pids[*]}"; do
        if [[ ! -z $i ]]; then
            echo "Killing PID: $i"
            kill -INT -$i 2> /dev/null
        fi
    done
    exit
}

trap pre_cleanup INT
trap cleanup EXIT

echo "Executing 5 temporary timeouts..."
unreachable_ip="192.168.44.5"
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null 
echo "ctrl+c now to execute cleanup"
my_timeout 9s ping -c 1 "$unreachable_ip" &> /dev/null 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.