我知道我可以通过执行以下操作来等待 bash 中的条件变为真:
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 var
在没有 fifo 的情况下使用,因为它会消耗 stdin,并且如果 stdin 不是终端或管道,则它将不起作用。
我可以用更优雅的方式避免子流程和忙碌等待吗?
答案1
bash
在较新版本(至少 v2)中,内置函数可以在运行时加载(通过)enable -f filename commandname
。许多这样的可加载内置函数也随 bash 源一起分发,并且sleep
位于其中。当然,可用性可能因操作系统(甚至机器不同)而异。例如,在 openSUSE 上,这些内置函数通过包分发bash-loadables
。
答案2
在内部循环中创建大量子进程是一件坏事。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
答案3
我最近有需要这样做。我想出了以下函数,可以让 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 || :
}
注意:我之前发布了一个每次都会打开和关闭文件描述符的版本,但我发现在某些系统上每秒执行数百次最终会锁定。因此,新的解决方案在函数调用之间保留文件描述符。无论如何,Bash 都会在退出时清理它。
这可以像 /bin/sleep 一样调用,并且它将在请求的时间内休眠。不带参数调用,它将永远挂起。
snore 0.1 # sleeps for 0.1 seconds
snore 10 # sleeps for 10 seconds
snore # sleeps forever
答案4
Inksh93
或mksh
,sleep
是一个内置 shell,因此另一种选择可能是使用这些 shell 而不是bash
。
zsh
还有一个zselect
内置函数(加载了zmodload zsh/zselect
),可以使用 休眠给定的百分之几秒zselect -t <n>
。