I know I can wait on a condition to become true in bash by doing:
while true; do test_condition && break sleep 1 done
But it creates 1 sub-process at each iteration (sleep). I could avoid them by doing:
while true; do test_condition && break done
But it uses lot of CPU (busy waiting). To avoid sub-processes and busy waiting, I came up with the solution bellow, but I find it ugly:
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.
Note: in the general case, I cannot simply use read -t 1 var without the fifo, because it will consume stdin, and will not work if stdin is not a terminal or a pipe.
Can I avoid sub-processes and busy waiting in a more elegant way ?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
In newer versions of bash (at least v2), builtins may be loaded (via enable -f filename commandname) at runtime. A number of such loadable builtins is also distributed with the bash sources, and sleep is among them. Availability may differ from OS to OS (and even machine to machine), of course. For example, on openSUSE, these builtins are distributed via the package bash-loadables.
Method 2
Creating a lot of subprocesses is a bad thing in an inner loop. Creating one sleep process per second is OK. There’s nothing wrong with
while ! test_condition; do sleep 1 done
If you really want to avoid the external process, you don’t need to keep the fifo open.
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
Method 3
I recently had a need to do this. I came up with the following function that will allow bash to sleep forever without calling any external program:
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 || :
}
NOTE: I previously posted a version of this that would open and close the file descriptor each time, but I found that on some systems doing this hundreds of times a second would eventually lock up. Thus the new solution keeps the file descriptor between calls to the function. Bash will clean it up on exit anyway.
This can be called just like /bin/sleep, and it will sleep for the requested time. Called without parameters, it will hang forever.
snore 0.1 # sleeps for 0.1 seconds snore 10 # sleeps for 10 seconds snore # sleeps forever
There’s a writeup with excessive details on my blog here
Method 4
As user yoi said, if in your script is stdin opened, then instead of sleep 1 you can simply use:
read -t 1 3<&- 3<&0 <&3
In Bash version 4.1 and newer you can use float number, e.g. read -t 0.3 ...
If in a script stdin is closed (script is called my_script.sh < /dev/null &), then you need use another opened descriptor, which not produces output when read is executed, eg. stdout:
read -t 1 <&1 3<&- 3<&0 <&3
If in a script all descriptor is closed (stdin, stdout, stderr) (e.g. because is called as daemon), then you need find any exists file which not produces output:
read -t 1 </dev/tty10 3<&- 3<&0 <&3
Method 5
In ksh93 or mksh, sleep is a shell builtin, so an alternative might be to use those shells instead of bash.
zsh also has a zselect builtin (loaded with zmodload zsh/zselect) that can sleep for a given number of hundredths of seconds with zselect -t <n>.
Method 6
This works from a login shell as well as a non-interactive shell.
#!/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
Method 7
Do you really need a fifo? Redirecting stdin to another file descriptor should work as well.
{
echo line | while read line; do
read -t 1 <&3
echo "$line"
done
} 3<&- 3<&0
Inspired by: Read input in bash inside a while loop
Method 8
A slight improvement on the above mentioned solutions (which I have based this on).
bash_sleep() {
read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}
# sleep for 10 seconds
bash_sleep 10
Reduced the need for a fifo and hence no cleaning up to do.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0