Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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 ?

share|improve this question
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. –  jfgagne 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. –  jfgagne Mar 17 '13 at 18:23
add comment

5 Answers 5

up vote 5 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.

share|improve this answer
    
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. –  jfgagne Mar 18 '13 at 9:28
    
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. –  jfgagne Mar 18 '13 at 12:46
add comment

Creating a lot of subprocesses is a bad thing in an inner loop. Creating one sleep process per second is peanuts. 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
share|improve this answer
    
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. –  jfgagne Mar 18 '13 at 9:25
add comment

In ksh93 sleep is a shell builtin, so an alternative might be to use that instead of bash.

share|improve this answer
    
Good to know, thanks. –  jfgagne Mar 17 '13 at 18:54
add comment

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
share|improve this answer
add comment

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

share|improve this answer
    
This is not doing a sleep, this is still consuming stdin from the terminal. –  jfgagne Mar 25 '13 at 10:27
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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