3

As I am a newbie in shell scripting, exec command always confuses me and while exploring this topic with while loop had triggered following 4 questions:

  1. What is the difference between the below syntax 1 and 2

    syntax 1:

    while read LINE
    do
    : # manipulate file here
    done < file
    

    syntax 2:

    exec n<&0 < file
     while read LINE
     do
       : # manipulate file here
     done
    exec 0<&n n<&-
    
  2. Kindly elaborate the operation of exec n<&0 < file lucidly

  3. Is this exec n<&0 < file command equivalent to exec n<file ? (if not then what's the difference between two?)

  4. I had read some where that in Bourne shell and older versions of ksh, a problem with the while loop is that it is executed in a subshell. This means that any changes to the script environment,such as exporting variables and changing the current working directory, might not be present after the while loop completes. As an example, consider the following script:

    #!/bin/sh
    if [ -f “$1” ] ; then
     i=0
      while read LINE
      do
       i=`expr $i + 1`
      done < “$1”
     echo $i
    fi
    

This script tries to count the number of lines in the file specified to it as an argument.

On executing this script on the file

$ cat dirs.txt
/tmp
/usr/local
/opt/bin
/var

can produce the following incorrect result: 0

Although you are incrementing the value of $i using the command i=expr $i + 1 when the while loop completes, the value of $i is not preserved.

In this case, you need to change a variable’s value inside the while loop and then use that value outside the loop.

One way to solve this problem is to redirect STDIN prior to entering the loop and then restore STDIN after the loop completes.

The basic syntax is

exec n<&0 < file while read LINE do : # manipulate file here done exec 0<&n n<&-

My question here is:

In Bourne shell and older versions of ksh,ALTHOUGH WHILE LOOP IS EXECUTED IN SUBSHELL, how this exec command here helps in retaining variable value even after while loop completion i.e. how does here exec command accomplishes the task change a variable’s value inside the while loop and then use that value outside the loop.

pawan vinayak
  • 185
  • 1
  • 2
  • 11

3 Answers3

2

So many questions... but all of them seem variants of the same one, so I'll go on...

exec without a command is used to do redirection in the current process. That is, it changes the files attached to different file descriptors (FD).

Question #1

I think it should be this way. In may system the {} are mandadory:

exec {n}<&0 < file

This line dups FD 0 (standard input) and stores the new FD into the n variable. Then it attaches file to the standard input.

while read LINE ; do ... done

This line reads lines into variable LINE from the standard input, that will be file.

exec 0<&n {n}<&-

And this line dups back the FD from n into 0 (the original standard input), that automatically closes file and then closes n (the dupped original stdin).

The other syntax:

while read LINE; do ... done < file

does the same, but in a less convoluted way.

Question #2

exec {n}<&0 < file

These are redirections, and they are executed left to right. The first one n<&0 does a dup(0) (see man dup) and stores the result new FD in variable n. Then the <file does a open("file"...) and assigns it to the FD 0.

Question #3

No. exec {n}<file opens the file and assigns the new FD to variable n, leaving the standard input (FD 0) untouched.

Question #4

I don't know about older versions of ksh, but the usual problem is when doing a pipe.

grep whatever | while read LINE; do ... done

Then the while command is run in a subshell. The same is true if it is to the left of the pipe.

while read LINE ; do ... done | grep whatever

But for simple redirects there is no subshell:

while read LINE ; do ... done < aaa > bbb

Extra unnumbered question

About your example script, it works for me once I've changed the typographic quotes to normal double quotes ;-):

#!/bin/sh
if [ -f "$1" ] ; then
 i=0
  while read LINE
  do
   i=`expr $i + 1`
  done < "$1"
 echo $i
fi

For example, if the file is test:

$ ./test test
9

And about your latest question, the subshell is not created by while but by the pipe | or maybe in older versions of ksh, by the redirection <. What the exec trick does is to prevent that redirection so no subshell is created.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
2

Let me answer your questions out-of-order.

Q #2

The command exec n<&0 < file is not valid syntax. Probably the n stands for "some arbitrary number". That said, for example

exec 3<&0 < file

executes two redirections, in sequence: it duplicates/copies the standard input file descriptor, which is 0, as file descriptor 3. Next, it "redirects" file descriptor 0 to read from file file.

Later, the command

exec 0<&3 3<&-

first copies back the standard input file descriptor from the saved file descriptor 3, redirecting standard input back to its previous source. Then it closes file descriptor 3, which has served its purpose to backup the initial stdin.

Q #1

Effectively, the two examples do the same: they temporarily redirect stdin within the scope of the while loop.

Q #3

Nope: exec 3<filename opens file filename using file descriptor 3. exec 3<&0 <filename I described in #2.

Q #4

I guess those older shells mentioned effectively executed

while ...; do ... ; done < filename

as

cat filename | while ...

thereby executing the while loop in a subshell.

Doing the redirections beforehand with those laborious exec commands avoids the redirection of the while block, and thereby the implicit sub-shell.

However, I never heard of that weird behavior, and I guess you won't have to deal with it unless you're working with truly ancient shells.

user2719058
  • 2,183
  • 11
  • 13
1
  1. The difference should be nothing in modern shells (both should be POSIX compatible), with some caveats:
    • There are likely thousands of unique shell binaries in use, some of which are missing common features or simply buggy.
    • Only the first version will behave as expected in an interactive shell, since the shell will close as soon as standard input gets EOF, which will happen once it finishes reading file.
    • The while loop reads from FD 0 in both cases, making the exec pointless if the shell supports < redirection to while loops. To read from FD 9 you have to use done <&9 (POSIX) or read -u 9 (in Bash).
  2. exec (in this case, see help exec/man exec) applies the redirections following it to the current shell, and they are applied left-to-right. For example, exec 9<&0 < file points FD 9 to where FD 0 (standard input) is currently pointing, effectively making FD 9 a "copy" of FD 0. After that file is sent to standard input (both FD 0 and 9).
  3. Run a shell within a shell to see the difference between the two (commented to explain):

    $ echo foo > file
    $ exec "$SHELL"
    $ exec 9<&0 < file
    $ foo # The contents of the file is executed in the shell
    bash: foo: command not found
    $ exit # Because the end of file, equivalent to pressing Ctrl-d
    $ exec "$SHELL"
    $ exec 9< file # Nothing happens, simply sends file to FD 9
    
  4. This is a common misconception about *nix shells: Variables declared in subshells (such as created by while) are not available to the parent shell. This is by design, not a bug. Many other answers here and on USE refer to this.
l0b0
  • 55,365
  • 30
  • 138
  • 223