web-dev-qa-db-ja.com

sleepコマンドなしで、bashで忙しい待機を回避する

次のようにすることで、bashで条件がtrueになるのを待つことができます。

while true; do
  test_condition && break
  sleep 1
done

ただし、反復(スリープ)ごとに1つのサブプロセスを作成します。私はそれらを避けることができました:

while true; do
  test_condition && break
done

しかし、それは多くのCPU(ビジー待機)を使用します。サブプロセスと忙しい待機を避けるために、私は以下の解決策を思いつきましたが、それは醜いです:

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

注:一般的なケースでは、fifoなしでread -t 1 varを単純に使用することはできません。stdinを消費し、stdinがターミナルまたはパイプでない場合は機能しないためです。

よりエレガントな方法でサブプロセスやビジー待機を回避できますか?

19
jfg956

bashの新しいバージョン(少なくともv2)では、ビルトインが実行時に(enable -f filename commandnameを介して)ロードされる場合があります。このようなロード可能なビルトインの多くはbashソースとともに配布されており、sleepもその中に含まれています。もちろん、可用性はOSによって(さらにはマシンごとに)異なる場合があります。たとえば、openSUSEでは、これらのビルトインはbash-loadablesパッケージを介して配布されます。

編集:パッケージ名を修正し、最低限のbashバージョンを追加します。

17

多くのサブプロセスを作成することは、内部ループでは悪いことです。毎秒1つのsleepプロセスを作成しても問題ありません。何も悪いことはありません

while ! test_condition; do
  sleep 1
done

外部プロセスを本当に避けたい場合は、FIFOを開いたままにする必要はありません。

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done

私は最近これを行う必要がありました。外部プログラムを呼び出さずにbashを永久にスリープさせる次の関数を思いつきました。

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

注:以前に、毎回ファイル記述子を開いたり閉じたりするバージョンを投稿しましたが、一部のシステムでは、これを1秒間に数百回実行すると、最終的にロックされます。したがって、新しいソリューションは、関数の呼び出し間でファイル記述子を保持します。とにかく、bashは終了時にそれをクリーンアップします。

これは/ bin/sleepのように呼び出すことができ、要求された時間だけスリープします。パラメータなしで呼び出されると、永久にハングします。

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

ここに私のブログに過度の詳細がある記事があります

7
bolt

ksh93またはmkshでは、sleepは組み込みのシェルであるため、bashの代わりにこれらのシェルを使用することもできます。

zshにはzselectビルトイン(zmodload zsh/zselectとともにロードされます)もあり、zselect -t <n>を使用して指定された100分の1秒の間スリープできます。

3
Scrutinizer

ユーザーyoiが言ったように、スクリプトでstdinが開かれている場合、代わりにスリープ1簡単に使用できます。

read -t 1 3<&- 3<&0 <&3

Bashバージョン4.1以降では、浮動小数点数を使用できます。 read -t 0.3 ...

スクリプト内stdinが閉じている場合(スクリプトはmy_script.sh < /dev/null &)の場合、readが実行されたときに出力を生成しない別の開かれた記述子を使用する必要があります。 stdout

read -t 1 <&1 3<&- 3<&0 <&3

スクリプト内ですべての記述子が閉じている場合(stdinstdoutstderr)(たとえば、デーモンとして呼び出されるため)、出力を生成しない既存のファイルを見つける必要があります。

read -t 1 </dev/tty10 3<&- 3<&0 <&3
2
Mysak

これは、ログインシェルと非インタラクティブシェルから機能します。

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3
1
Omar

上記のソリューション(私はこれに基づいています)のわずかな改善。

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

Fifoの必要性が減ったため、クリーンアップを行う必要がなくなりました。

0
CodeMedic

あなたは本当にFIFOが必要ですか? stdinを別のファイル記述子にリダイレクトすることもできます。

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

触発: whileループ内のbashで入力を読み取る

0
yoi