1. ホーム
  2. バッシュ

[解決済み】bashで不要な遅延を発生させずにコマンドをタイムアウトさせる

2022-04-29 03:30:43

質問

この回答 から 一定時間後にコマンドを自動終了させるコマンドラインコマンド

は、bash のコマンドラインから長時間実行するコマンドをタイムアウトさせる 1 行のメソッドを提案しています。

( /path/to/slow command with options ) & sleep 5 ; kill $!

しかし、与えられた "long-running"コマンドがタイムアウトより早く終了する可能性があります。

(このコマンドを、quot;typically-long-running but-sometimes-fast" と呼ぶことにします。 tlrbsf を、お楽しみに)

この気の利いた1ライナーのアプローチには、いくつかの問題があるわけです。

まず sleep は条件付きではないので、シーケンスが終了するまでにかかる時間に望ましくない下限が設定されます。30秒とか2mとか5mとかのスリープを考えてみてください。 tlrbsf コマンドは2秒で終了するため、非常に好ましくありません。

次に kill は無条件なので、このシーケンスは実行されていないプロセスを kill しようとし、それについて泣き言を言うでしょう。

それで...

方法はありますか? をタイムアウトさせるには、通常長く実行されるが時々速い ( "tlrbsf" というコマンドを実行します。

  • はbashの実装があります(他の質問にはすでにPerlとCの回答があります)。
  • はどちらか早い方で終了します。 tlrbsf プログラムの終了、またはタイムアウトの経過
  • は、存在しない、あるいは実行されていないプロセスを kill しない(あるいは、オプションで 文句を言う を実行します。)
  • 1ライナーである必要はない
  • CygwinまたはLinuxで実行可能

...そして、ボーナスポイントとして

  • が実行されます。 tlrbsf コマンドをフォアグラウンドで実行します。
  • バックグラウンドの 'スリープ' または余分なプロセス

のstdin/stdout/stderrが、そのような tlrbsf コマンドを直接実行した場合と同じように、リダイレクトすることができますか?

もしそうなら、あなたのコードを共有してください。そうでない場合は、その理由を説明してください。

前述の例をハックするのにしばらく費やしましたが、私のbashスキルの限界にぶつかっています。

どのように解決するのですか?

まさにご指摘の通りだと思います。

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $$ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $$ && kill -0 $$ || exit 0
    sleep $delay
    kill -s SIGKILL $$
) 2> /dev/null &

exec "$@"