スリープコマンドなしで、bashでビジーな待機を避ける


19

次のことを行うことで、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.

注:一般的な場合、read -t 1 varfifoなしでは単純に使用できません。これはstdinを消費し、stdinが端末またはパイプでない場合は機能しないためです。

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


1
trueは組み込みであり、bashでサブプロセスを作成しません。忙しい待機は常に悪いでしょう。
ヨルダン

@joranm:あなたは正しいですtrue、質問が更新されました。
jfg956

なぜfifoがないのですか?単にread -t 1 var
-ott--

@ott:あなたは正しいですが、これは標準入力を消費します。また、stdinが端末でもパイプでもない場合は機能しません。
jfg956

保守性が問題になる場合はsleep、最初の例のように進めることを強くお勧めします。2つ目は、動作する可能性はありますが、将来調整するのは簡単ではありません。単純なコードは、安全である可能性も大きくなります。
クサラナナンダ

回答:


17

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

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


うわー、これは私が探しているものであり、私は間違いなくロード可能な組み込みについて何かを学びます:+1。これを試してみますが、それでも最良の答えです。
jfg956

1
できます !Debianでは、パッケージはbash-builtinsです。ソースのみが含まれており、Makefileを編集する必要がありますがsleep、ビルトインとしてインストールできました。ありがとう。
jfg956

9

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

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

1秒あたりのプロセスがピーナッツであることは正しいです(ただし、私の質問はそれを削除する方法を見つけることでした)。短いバージョンについては、それは私のものよりも良いので、+ 1(しかし、私mkdirはそれが行われたように削除しましたmktemp(そうでない場合、それは競合状態です))。while ! test_condition;私の最初の解決策よりも優れていることについても真実です。
jfg956

7

私は最近これをする必要がありました。外部プログラムを呼び出さずに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

ここに私のブログに過剰な詳細を記載した記事があります


1
優れたブログエントリ。しかし、10秒間待ちread -t 10 < <(:)ながらすぐに戻る理由の説明を求めてそこに行きましたがread -t 10 <> <(:)、まだわかりません。
アミール

ではread -t 10 <> <(:)何ん<>の略?
CodeMedic

<>は、基になるプロセス置換<(:)が読み取りのみを許可している場合でも、読み取りおよび書き込み用にファイル記述子を開きます。これはLinuxを引き起こすハックであり、特にLinuxは、誰かが書き込みを行う可能性があると想定するため、読み取りは到着しない入力を待ってハングします。これは、回避策はにキックされます。その場合にはBSDシステム上でこれを行うことはありません。
ボルト

3

ではksh93またはmkshsleep代替の代わりにこれらのシェルを使用するかもしれないので、シェル組み込みですbash

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


2

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

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

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

スクリプトにした場合、標準入力(スクリプトが呼び出され、閉じているmy_script.sh < /dev/null &)、そして、あなたは時に出力を生成していない別の開いた記述、使用する必要が読み出しが実行され、例えば。標準出力

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

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

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

read -t 1 3<&- 3<&0 <&3はと同じread -t 0です。これは、タイムアウトを指定してstdinから読み取っているだけです。
ステファンシャゼラス

1

これは、ログインシェルおよび非対話型シェルから機能します。

#!/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

これは、Mac OS X v10.12.6
b01

1
これは推奨されません。複数のスクリプトが同時にこれを使用する場合、それらはすべてstdinを読み取ろうとするため、すべてSIGSTOPされます。これが待機している間、stdinはブロックされます。これにはstdinを使用しないでください。新しい異なるファイル記述子が必要です。
18

1
@Normadizeここには別の答えがあります(unix.stackexchange.com/a/407383/147685)。これは無料のファイル記述子の使用に関する懸念を扱っています。その最低限のバージョンはread -t 10 <> <(:)です。
アミール

0

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

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

霊感を受けた:whileループ内でbashで入力を読み取る


1
これはスリープ状態ではなく、ターミナルからまだstdinを消費しています。
jfg956

0

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

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

# sleep for 10 seconds
bash_sleep 10

fifoの必要性を減らしたため、クリーニングが不要になりました。


1
これは推奨されません。複数のスクリプトが同時にこれを使用する場合、それらはすべてstdinを読み取ろうとするため、すべてSIGSTOPされます。これが待機している間、stdinはブロックされます。これにはstdinを使用しないでください。新しい異なるファイル記述子が必要です。
18

@Normadizeそのことを考えたことはありません。詳しい説明が必要なリソースを詳しく説明してください。
CodeMedic

@CodeMedicここには別の答えがあります(unix.stackexchange.com/a/407383/147685)。これは、無料のファイル記述子を使用することの懸念を扱っています。その最低限のバージョンはread -t 10 <> <(:)です。
アミール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.