シェルスクリプトのタイムアウト

シェルスクリプトのタイムアウト

シェルスクリプトそれは標準入力から読む。入力を提供する準備ができている人がいない場合、スクリプトは次のことを行う必要があります。停止する。タイムアウトになると、スクリプトはいくつかのクリーンアップコードを実行する必要があります。最良のアプローチは何ですか?

このスクリプトは次のようにする必要があります。非常に携帯可能20世紀のUnixシステムとCコンパイラなしでbusyboxを実行する組み込みデバイスが含まれているため、Perl、bash、コンパイルされた言語、またはPOSIX.2全体も信頼できません。特にPOSIX$PPIDread -t完全互換のトラップは使用できません。一時ファイルへの書き込みも扱いません。すべてのファイルシステムが読み取り専用でマウントされている場合でも、スクリプトを実行できます。

もっと難しくするだけです。台本に意味があったらいいのに早くタイムアウトしないとき。特に私はWindows(主にCygwin)でもスクリプトを使用しますが、ここではforkとexecが特に低いレベルなので、その使用を最小限に保ちたいと思います。

要するに、私は

trap cleanup 1 2 3 15
foo=`cat`

タイムアウトを追加したいです。内蔵型catに交換できません。readタイムアウトが発生した場合は、関数を実行したいと思いますcleanup


背景:このスクリプトは一部の8ビット文字を印刷し、前のカーソル位置と次のカーソル位置を比較して端末のエンコーディングを推測します。スクリプトの冒頭では、stdoutがサポートされている端末に接続されているかどうかをテストします。plinkTERM=xterm呼び出されても設定されます。TERM=dumb)。スクリプトの関連部分は次のとおりです。

text='Éé'  # UTF-8; shows up as Ãé on a latin1 terminal
csi='␛['; dsr_cpr="${csi}6n"; dsr_ok="${csi}5n"  # ␛ is an escape character
stty_save=`stty -g`
cleanup () { stty "$stty_save"; }
trap 'cleanup; exit 120' 0 1 2 3 15     # cleanup code
stty eol 0 eof n -echo                # Input will end with `0n`
# echo-n is a function that outputs its argument without a newline
echo-n "$dsr_cpr$dsr_ok"              # Ask the terminal to report the cursor position
initial_report=`tr -dc \;0123456789`  # Expect ␛[42;10R␛[0n for y=42,x=10
echo-n "$text$dsr_cpr$dsr_ok"
final_report=`tr -dc \;0123456789`
cleanup
# Compute and return initial_x - final_x

tr2秒後に入力を読み取らない場合は、スクリプトを終了して関数を実行cleanupするようにスクリプトを変更するにはどうすればよいですか?

答え1

これはどうですか?

foo=`{ { cat 1>&3; kill 0; } | { sleep 2; kill 0; } } 3>&1`

つまり、出力生成コマンドを実行し、sleep同じプロセスグループにそのプロセスグループを作成します。最初に返されるコマンドは、プロセスグループ全体を終了します。

気になるかもしれません。はい、パイプをバイパスするためにリダイレクトを使用しません。唯一の目的は、シェルに同じプロセスグループで両方のプロセスを実行させることです。


Gillesがコメントで指摘したように、スクリプトプロセスは2つのサブプロセスで終了するため、シェルスクリプトでは機能しません。

別のプロセスグループでコマンドを強制的に実行する1つの方法は、新しい対話型シェルを起動することです。

#!/bin/sh
foo=`sh -ic '{ cat 1>&3; kill 0; } | { sleep 2; kill 0; }' 3>&1 2>/dev/null`
[ -n "$foo" ] && echo got: "$foo" || echo timeouted

ただし、いくつかの注意があります(たとえば、stdinがttyではない場合)。 stderrリダイレクトは、対話型シェルが終了したときに「終了しました」メッセージを削除するためのものです。

zshbashおよびテストを使用してくださいdash。しかし、古いものはどうですか?

B98GNU bash 3.2.57 を使用する Mac OS X またはダッシュを使用する Linux では、次の変更をお勧めします。

foo=`sh -ic 'exec 3>&1 2>/dev/null; { cat 1>&3; kill 0; } | { sleep 2; kill 0; }'`


1.setsid一見非標準的な状態を除いて。

答え2

me=$$
(sleep 2; kill $me >/dev/null 2>&1) & nuker=$!
# do whatever
kill $nuker >/dev/null 2>&1

SIGTERM15個(特に指定のない限り、数字バージョンが送信されます)をキャプチャしたので、killこれはうまくいきます。つまり、POSIX以前のバージョンを見ている場合は、シェル機能も存在しない可能性があることに注意してください(System Vシェルで提供されています)。

答え3

coretuils バージョン 7.0 には timeout コマンドが含まれていますが、一部の環境にはこのコマンドがないと述べました。幸いなことにPixelbeat.orgタイムアウトスクリプトを作成しましたsh

以前は何度も使ってきて、良い結果が出ました。

http://www.pixelbeat.org/scripts/timeoutメモ:以下のスクリプトはPixelbeat.orgのスクリプトで少し修正されました。この回答の下の説明を参照してください。 )

#!/bin/sh

# Execute a command with a timeout

# Author:
#    http://www.pixelbeat.org/
# Notes:
#    Note there is a timeout command packaged with coreutils since v7.0
#    If the timeout occurs the exit status is 124.
#    There is an asynchronous (and buggy) equivalent of this
#    script packaged with bash (under /usr/share/doc/ in my distro),
#    which I only noticed after writing this.
#    I noticed later again that there is a C equivalent of this packaged
#    with satan by Wietse Venema, and copied to forensics by Dan Farmer.
# Changes:
#    V1.0, Nov  3 2006, Initial release
#    V1.1, Nov 20 2007, Brad Greenlee <[email protected]>
#                       Make more portable by using the 'CHLD'
#                       signal spec rather than 17.
#    V1.3, Oct 29 2009, Ján Sáreník <[email protected]>
#                       Even though this runs under dash,ksh etc.
#                       it doesn't actually timeout. So enforce bash for now.
#                       Also change exit on timeout from 128 to 124
#                       to match coreutils.
#    V2.0, Oct 30 2009, Ján Sáreník <[email protected]>
#                       Rewritten to cover compatibility with other
#                       Bourne shell implementations (pdksh, dash)

if [ "$#" -lt "2" ]; then
    echo "Usage:   `basename $0` timeout_in_seconds command" >&2
    echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
    exit 1
fi

cleanup()
{
    trap - ALRM               #reset handler to default
    kill -ALRM $a 2>/dev/null #stop timer subshell if running
    kill $! 2>/dev/null &&    #kill last job
      exit 124                #exit with 124 if it was running
}

watchit()
{
    trap "cleanup" ALRM
    sleep $1& wait
    kill -ALRM $$
}

watchit $1& a=$!         #start the timeout
shift                    #first param was timeout for sleep
trap "cleanup" ALRM INT  #cleanup after timeout
"$@" < /dev/tty & wait $!; RET=$?    #start the job wait for it and save its return value
kill -ALRM $a            #send ALRM signal to watchit
wait $a                  #wait for watchit to finish cleanup
exit $RET                #return the value

答え4

独自のプロセスグループでパイプを実行するもう1つの方法は、疑似端末sh -c '....'script関数が暗黙的に適用される場所setsid)でrunコマンドを使用することです。

#!/bin/sh
stty -echo -onlcr
# GNU script
foo=`script -q -c 'sh -c "{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }" 3>&1 2>/dev/null' /dev/null`
# FreeBSD script
#foo=`script -q /dev/null sh -c '{ cat 1>&3; kill 0; } | { sleep 5; kill 0; }' 3>&1 2>/dev/null`
stty echo onlcr
echo "foo: $foo"


# alternative without: stty -echo -onlcr
# cr=`printf '\r'`
# foo=`script -q -c 'sh -c "{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null"' /dev/null | sed -e "s/${cr}$//" -ne 'p;N'`  # GNU
# foo=`script -q /dev/null sh -c '{ { cat 1>&3; kill 0; } | { sleep 5; kill 0; } } 3>&1 2>/dev/null' | sed -e "s/${cr}$//" -ne 'p;N'`  # FreeBSD
# echo "foo: $foo"

関連情報