wardva
wardva

Reputation: 624

Bash while loop stops unexpectedly

I'm analyzing two scripts with some behaviour I don't understand:

#/bin/bash
tijd=${1-60}
oud=`ls -l $MAIL`
while : ; do
   nieuw=`ls -l $MAIL`
   echo $oud $nieuw
   sleep $tijd
done | { read a b rest ; echo $a ; echo $b ; echo $rest ; }

The while loop in this script stops after one iteration.

#/bin/bash
tijd=${1-60}
oud=`ls -l $MAIL`
while : ; do
   nieuw=`ls -l $MAIL`
   echo $oud $nieuw
   sleep $tijd
done | cat

The while loop in this script is infinite.

What is the difference? I think it's something with the pipe and the brackets but I can't explain it.

Upvotes: 6

Views: 2541

Answers (2)

anubhava
anubhava

Reputation: 784898

Your loop with read after pipe is terminating after first iteration because of invocation of SIGPIPE signal which happens LHS of pipe writes to a pipe whose output is not read as there is no while loop around read on RHS). YOur example with cat doesn't exit because cat continuously reads from input where as read terminates after reading one line.

To understand this behavior first reduce your example:

while : ; do pwd; done | { read -r line; echo $line; }
/Users/admin

So read terminated after first line. To verify this enable pipefail using:

set -o pipefail

and check exit status:

while : ; do pwd; done | { read -r line; echo "$line"; }
echo $?
141

Exist status 141 is due to SIGPIPE.


To fix this now change your read on RHS of pipe inside a while loop:

while : ; do pwd; sleep 5; done | { while read -r line; do echo "$line"; done; }
/Users/admin
/Users/admin
/Users/admin

And now you will not see command exiting at all since while read is continuously capturing all the data from LHS of pipe.

Upvotes: 3

Borut Hadžialić
Borut Hadžialić

Reputation: 371

The problem is that the {read a b rest; ..} block is executed only once, and that the read command reads one line of input, while you expect it to read many lines.

What you wanted to write is this:

#/bin/bash
tijd=${1-60}
oud=`ls -l $MAIL`
while read a b rest; do
  echo $a
  echo $b
  echo $rest
done < <(
  while : ; do
    nieuw=`ls -l $MAIL`
    echo $oud $nieuw
    sleep $tijd
  done
)

It is a more 'standard' way to do it. It also avoids using the pipe |, which forces the {read ..} block to run as a separate process, which then makes it impossible to observe the effects of the read command in your main shell.

And the <( .. ) thing is called process substitution. It basically executes the contained block while capturing its output into a temporary file, and then returns that temporary file name.

Upvotes: 0

Related Questions