次のようにすることで、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がターミナルまたはパイプでない場合は機能しないためです。
よりエレガントな方法でサブプロセスやビジー待機を回避できますか?
bash
の新しいバージョン(少なくともv2)では、ビルトインが実行時に(enable -f filename commandname
を介して)ロードされる場合があります。このようなロード可能なビルトインの多くはbashソースとともに配布されており、sleep
もその中に含まれています。もちろん、可用性はOSによって(さらにはマシンごとに)異なる場合があります。たとえば、openSUSEでは、これらのビルトインはbash-loadables
パッケージを介して配布されます。
編集:パッケージ名を修正し、最低限のbashバージョンを追加します。
多くのサブプロセスを作成することは、内部ループでは悪いことです。毎秒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
ksh93
またはmksh
では、sleep
は組み込みのシェルであるため、bash
の代わりにこれらのシェルを使用することもできます。
zsh
にはzselect
ビルトイン(zmodload zsh/zselect
とともにロードされます)もあり、zselect -t <n>
を使用して指定された100分の1秒の間スリープできます。
ユーザー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
スクリプト内ですべての記述子が閉じている場合(stdin、stdout、stderr)(たとえば、デーモンとして呼び出されるため)、出力を生成しない既存のファイルを見つける必要があります。
read -t 1 </dev/tty10 3<&- 3<&0 <&3
これは、ログインシェルと非インタラクティブシェルから機能します。
#!/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
上記のソリューション(私はこれに基づいています)のわずかな改善。
bash_sleep() {
read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}
# sleep for 10 seconds
bash_sleep 10
Fifoの必要性が減ったため、クリーンアップを行う必要がなくなりました。
あなたは本当にFIFOが必要ですか? stdinを別のファイル記述子にリダイレクトすることもできます。
{
echo line | while read line; do
read -t 1 <&3
echo "$line"
done
} 3<&- 3<&0