A new twist on while-loops

kenJackson

Member
If you've ever tried to use a while-loop like this, you've experienced some frustration.
Code:
#!/bin/sh
RESULT=
cat db | while read index color; do
    test "$index" = 2  &&  RESULT="$color"
done
echo "Index 2 is \"$RESULT\""
Where I'm using this file named db:
Code:
1  red
2  green
3  blue
When you run it, you get this result:
Code:
Index 2 is ""
The problem is that since I'm piping into the while loop, it's run in a subprocess so changes to variables don't affect the parent's environment.

In this case it can be solved by redirection like this, but it seems all too easy to accidentally end up with all or part of the loop in a subprocess some other way, which results in unexpected failures.
Code:
done < db

But here's what I just learned.
This is a new technique, at least to me. (Has anyone used this before?)
Code:
#!/bin/sh
RESULT=
exec 7< db
while read index color <&7; do
    test "$index" = 2  &&  RESULT="$color"
done
exec 7<&-
echo "Index 2 is \"$RESULT\""
Notice the "exec 7< db" opens the file, "<&7" redirects input, and "exec 7<&-" closes it. Any available file descriptor number greater than 2 can be used.

Of course it's still possible to accidentally put something into a subprocess, but this just seems a little cleaner to me.
 


JasKinasis

Well-Known Member
Yes, I've seen input redirection used like that before.

In this specific example, using input redirection would be cleaner and easier than using a pipe, which involves a sub-shell. Personally, I would probably have used the "done < db" option in this example, rather than using the "named" redirection using "exec 7< db" etc.

IDK the exact term for this kind of redirection. But it's similar to creating a named pipe. Essentially the initial "exec 7< db" defines input stream 7 as the file db, then you redirect input from stream 7 into your while statement and then you close the file/stream when you are finished with it.

But there are some times where using a pipe is unavoidable - in those scenarios, I'd enclose the main operation in a sub-shell like this:
Code:
#!/bin/env bash
RESULT=$( cat db | while read index color; do
    test "$index" = 2  &&  echo "$color"
done )
echo "Index 2 is \"$RESULT\""
In the above example, we do our piping and our while loop in a subshell and echo the value of $color when $index is 2.
The echoed output from our subshell is stored in $RESULT and then output to the screen in the scripts final echo statement.

Again, in this particular example - using input redirection (as per your later examples) is more appropriate than using cat and pipe (which in turn, invokes a sub-shell). But if we were doing something different and a pipe was unavoidable, I'd do something like the above and enclose the entire operation in a sub-shell.

At the end of the day, it all depends on what you're trying to do. Sometimes input redirection is cleaner, faster and can avoid having to use unnecessary sub-shells. But there are some times where a sub-shell is unavoidable, or even helpful. I know some people go to great lengths to optimise their scripts by avoiding using sub-shells as far as possible. I take a more pragmatic approach.

Sometimes it is quicker and easier to use a sub-shell. Just not in this particular instance!
 
My apologies in advance ....
.... this is barely on-topic and of no practical use in solving the problem

But I saw the thread title , and I just couldn't resist :-

while ( ! ( succeed = try ( ) ) ) ;

:D
 

Members online


Top