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 ?

  • 1
    true is a builtin and does not create a sub process in bash. busy waiting will always be bad. – jordanm Mar 17 '13 at 17:47
  • @joranm: you are right about true, question updated. – jfg956 Mar 17 '13 at 17:59
  • Why not without fifo? Simply read -t 1 var. – ott-- Mar 17 '13 at 18:21
  • @ott: you are right, but this will consume stdin. Also, it will not work if stdin is not a terminal or a pipe. – jfg956 Mar 17 '13 at 18:23
  • If maintainability is an issue, I'd would strongly suggest going with the sleep as in the first example. The second one, while it may work, is not going to be easy for anyone to adjust in the future. Simple code also have bigger potential for being safe. – Kusalananda Jan 31 at 9:33
up vote 15 down vote accepted

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.

Edit: fix package name, add minimum bash version.

  • Wow, this is what I am looking for, and I definitely learn something about loadable builtin: +1. I will try this, and yet it is the best answer. – jfg956 Mar 18 '13 at 9:28
  • 1
    It works ! On debian, the package is bash-builtins. It only includes sources and the Makefile must be edited, but I was able to install sleep as a builtin. Thanks. – jfg956 Mar 18 '13 at 12:46

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
  • You are right about a process per second being peanuts (but my question was about finding a way to remove it). About the shorter version, It is nicer than mine, so +1 (but I removed the mkdir as it is done by mktemp (if not, it is a race condition)). Also true about the while ! test_condition; which is nicer than my initial solution. – jfg956 Mar 18 '13 at 9:25

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()
{
    [[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:)
    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

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>.

  • Good to know, thanks. – jfg956 Mar 17 '13 at 18:54

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
  • read -t 1 3<&- 3<&0 <&3 is the same as read -t 0. It's just reading from from stdin with timeout. – Stéphane Chazelas Oct 13 '16 at 20:01

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
  • This also worked on Mac OS X v10.12.6 – b01 May 31 at 14:15
  • This is not recommended. If multiple scripts use this at the same time then they all get SIGSTOP'ed as they all try to read stdin. Your stdin gets blocked while this waits. Don't use stdin for this. You want new different file descriptors. – Normadize Sep 22 at 12:28

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

  • 1
    This is not doing a sleep, this is still consuming stdin from the terminal. – jfg956 Mar 25 '13 at 10:27

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.

  • This is not recommended. If multiple scripts use this at the same time then they all get SIGSTOP'ed as they all try to read stdin. Your stdin gets blocked while this waits. Don't use stdin for this. You want new different file descriptors. – Normadize Sep 22 at 12:26
  • @Normadize Never thought of that; please can you elaborate or point me to a resource where I can read more about it. – CodeMedic Sep 22 at 13:50

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.