[編集:これは、生成されたすべてのプロセスを強制終了する方法を尋ねる他のいくつかの質問に似ています–答えはすべてpkillを使用することです。したがって、私の質問の核心は次のようになるかもしれません:スクリプトによって生成されたすべてのプロセスにCtrl-C/Zを伝播する方法はありますか?]
Coreutilsからrec
コマンドを使用してSoX timeout
を呼び出す場合(議論 here )、キーストロークでそれを強制終了する方法はないようです。 Bashスクリプト内から呼び出されました。
例:
timeout 10 rec test.wav
...で殺すことができます Ctrl+C または Ctrl+Z bashからですが、スクリプト内から呼び出されたときはそうではありません。
timeout 10 ping nowhere
...で殺すことができます Ctrl+C または Ctrl+Z bashから、そして Ctrl+Z スクリプト内から実行した場合。
プロセスIDを見つけてそのように終了することはできますが、標準のブレークキーストロークを使用できないのはなぜですか。そして、私ができるようにスクリプトを構成する方法はありますか?
などの信号キー 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
押すと Ctrl+C 2秒後、これはまだ5秒間完全に待機してから、「中断」メッセージを出力します。これは、フォアグラウンドジョブがアクティブな間、シェルがトラップコードの実行を控えるためです。
これを修正するには、ジョブをバックグラウンドで実行します。シグナルハンドラーでkill
を呼び出して、シグナルをtimeout
プロセスグループにリレーします。
#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid
Gillesによって提供された優れた答えに基づいて構築します。タイムアウトコマンドにはフォアグラウンドオプションがあり、使用すると、CTRL + Cでタイムアウトコマンドを終了します。
#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date
基本的に Ctrl+CSIGINT
シグナルを送信しますが、 Ctrl+ ZはSIGTSTP
シグナルを送信します。
SIGTSTP
はプロセスを停止するだけで、SIGCONT
はプロセスを続行します。
これは、コマンドラインでフォークされたフォアグラウンドプロセスで機能します。
プロセスがバックグラウンドプロセスの場合は、別の方法でそのシグナルをプロセスに送信する必要があります。 kill
がそれを行います。理論的には、そのkillの「-」演算子も子プロセスにシグナルを送る必要がありますが、これが期待どおりに機能することはめったにありません。
さらに読むために Unix-Signals
既存のスクリプトに「検索と置換」のアプローチを提供することで @ Gilles 'answer を改善します。
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/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
}
INT
ハンドラーに追加(またはマージ)します。pre_cleanup(){
exec 1>&21; exec 2>&22 # restore file descriptors, see https://superuser.com/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
timeout
コマンドをmy_timeout
関数に置き換えます。次にスクリプトの例を示します。
#!/bin/bash
# see "killing timeout": https://unix.stackexchange.com/a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/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 https://superuser.com/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