Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
Prev | Chapter 20. I/O Redirection | Next

# 20.2. Redirecting Code Blocks

Blocks of code, such as while, until, and for loops, even if/then test blocks can also incorporate redirection of `stdin`. Even a function may use this form of redirection (see Example 24-11). The < operator at the end of the code block accomplishes this.

Example 20-5. Redirected while loop

       1 #!/bin/bash
       2 # redir2.sh
       3
       4 if [ -z "$1" ]
       5 then
       6   Filename=names.data       # Default, if no filename specified.
       7 else
       8   Filename=$1
       9 fi
      10 #+ Filename=${1:-names.data}
      11 #  can replace the above test (parameter substitution).
      12
      13 count=0
      14
      15 echo
      16
      17 while [ "$name" != Smith ]  # Why is variable $name in quotes?
      18 do
      19   read name                 # Reads from $Filename, rather than stdin.
      20   echo $name
      21   let "count += 1"
      22 done <"$Filename"           # Redirects stdin to file $Filename.
      23 #    ^^^^^^^^^^^^
      24
      25 echo; echo "$count names read"; echo
      26
      27 exit 0
      28
      29 #  Note that in some older shell scripting languages,
      30 #+ the redirected loop would run as a subshell.
      31 #  Therefore, $count would return 0, the initialized value outside the loop.
      32 #  Bash and ksh avoid starting a subshell *whenever possible*,
      33 #+ so that this script, for example, runs correctly.
      34 #  (Thanks to Heiner Steven for pointing this out.)
      35
      36 #  However . . .
      37 #  Bash *can* sometimes start a subshell in a PIPED "while-read" loop,
      38 #+ as distinct from a REDIRECTED "while" loop.
      39
      40 abc=hi
      41 echo -e "1\n2\n3" | while read l
      42      do abc="$l"
      43         echo $abc
      44      done
      45 echo $abc
      46
      47 #  Thanks, Bruno de Oliveira Schneider, for demonstrating this
      48 #+ with the above snippet of code.
      49 #  And, thanks, Brian Onn, for correcting an annotation error.

Example 20-6. Alternate form of redirected while loop

       1 #!/bin/bash
       2
       3 # This is an alternate form of the preceding script.
       4
       5 #  Suggested by Heiner Steven
       6 #+ as a workaround in those situations when a redirect loop
       7 #+ runs as a subshell, and therefore variables inside the loop
       8 # +do not keep their values upon loop termination.
       9
      10
      11 if [ -z "$1" ]
      12 then
      13   Filename=names.data     # Default, if no filename specified.
      14 else
      15   Filename=$1
      16 fi
      17
      18
      19 exec 3<&0                 # Save stdin to file descriptor 3.
      20 exec 0<"$Filename"        # Redirect standard input.
      21
      22 count=0
      23 echo
      24
      25
      26 while [ "$name" != Smith ]
      27 do
      28   read name               # Reads from redirected stdin ($Filename).
      29   echo $name
      30   let "count += 1"
      31 done                      #  Loop reads from file $Filename
      32                           #+ because of line 20.
      33
      34 #  The original version of this script terminated the "while" loop with
      35 #+      done <"$Filename"
      36 #  Exercise:
      37 #  Why is this unnecessary?
      38
      39
      40 exec 0<&3                 # Restore old stdin.
      41 exec 3<&-                 # Close temporary fd 3.
      42
      43 echo; echo "$count names read"; echo
      44
      45 exit 0

Example 20-7. Redirected until loop

       1 #!/bin/bash
       2 # Same as previous example, but with "until" loop.
       3
       4 if [ -z "$1" ]
       5 then
       6   Filename=names.data         # Default, if no filename specified.
       7 else
       8   Filename=$1
       9 fi
      10
      11 # while [ "$name" != Smith ]
      12 until [ "$name" = Smith ]     # Change  !=  to =.
      13 do
      14   read name                   # Reads from $Filename, rather than stdin.
      15   echo $name
      16 done <"$Filename"             # Redirects stdin to file $Filename.
      17 #    ^^^^^^^^^^^^
      18
      19 # Same results as with "while" loop in previous example.
      20
      21 exit 0

Example 20-8. Redirected for loop

       1 #!/bin/bash
       2
       3 if [ -z "$1" ]
       4 then
       5   Filename=names.data          # Default, if no filename specified.
       6 else
       7   Filename=$1
       8 fi
       9
      10 line_count=`wc $Filename | awk '{ print $1 }'`
      11 #           Number of lines in target file.
      12 #
      13 #  Very contrived and kludgy, nevertheless shows that
      14 #+ it's possible to redirect stdin within a "for" loop...
      15 #+ if you're clever enough.
      16 #
      17 # More concise is     line_count=$(wc -l < "$Filename")
      18
      19
      20 for name in `seq $line_count`  # Recall that "seq" prints sequence of numbers.
      21 # while [ "$name" != Smith ]   --   more complicated than a "while" loop   --
      22 do
      23   read name                    # Reads from $Filename, rather than stdin.
      24   echo $name
      25   if [ "$name" = Smith ]       # Need all this extra baggage here.
      26   then
      27     break
      28   fi
      29 done <"$Filename"              # Redirects stdin to file $Filename.
      30 #    ^^^^^^^^^^^^
      31
      32 exit 0

We can modify the previous example to also redirect the output of the loop.

Example 20-9. Redirected for loop (both `stdin` and `stdout` redirected)

       1 #!/bin/bash
       2
       3 if [ -z "$1" ]
       4 then
       5   Filename=names.data          # Default, if no filename specified.
       6 else
       7   Filename=$1
       8 fi
       9
      10 Savefile=$Filename.new         # Filename to save results in.
      11 FinalName=Jonah                # Name to terminate "read" on.
      12
      13 line_count=`wc $Filename | awk '{ print $1 }'`  # Number of lines in target file.
      14
      15
      16 for name in `seq $line_count`
      17 do
      18   read name
      19   echo "$name"
      20   if [ "$name" = "$FinalName" ]
      21   then
      22     break
      23   fi
      24 done < "$Filename" > "$Savefile"     # Redirects stdin to file $Filename,
      25 #    ^^^^^^^^^^^^^^^^^^^^^^^^^^^       and saves it to backup file.
      26
      27 exit 0

Example 20-10. Redirected if/then test

       1 #!/bin/bash
       2
       3 if [ -z "$1" ]
       4 then
       5   Filename=names.data   # Default, if no filename specified.
       6 else
       7   Filename=$1
       8 fi
       9
      10 TRUE=1
      11
      12 if [ "$TRUE" ]          # if true    and   if :   also work.
      13 then
      14  read name
      15  echo $name
      16 fi <"$Filename"
      17 #  ^^^^^^^^^^^^
      18
      19 # Reads only first line of file.
      20 # An "if/then" test has no way of iterating unless embedded in a loop.
      21
      22 exit 0

Example 20-11. Data file names.data for above examples

       1 Aristotle
       2 Arrhenius
       3 Belisarius
       4 Capablanca
       5 Dickens
       6 Euler
       7 Goethe
       8 Hegel
       9 Jonah
      10 Laplace
      11 Maroczy
      12 Purcell
      13 Schmidt
      14 Schopenhauer
      15 Semmelweiss
      16 Smith
      17 Steinmetz
      18 Tukhashevsky
      19 Turing
      20 Venn
      21 Warshawski
      22 Znosko-Borowski
      23
      24 #  This is a data file for
      25 #+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh".

Redirecting the `stdout` of a code block has the effect of saving its output to a file. See Example 3-2.

Here documents are a special case of redirected code blocks. That being the case, it should be possible to feed the output of a here document into the `stdin` for a while loop.

       1 # This example by Albert Siersema
       2 # Used with permission (thanks!).
       3
       4 function doesOutput()
       5  # Could be an external command too, of course.
       6  # Here we show you can use a function as well.
       7 {
       8   ls -al *.jpg | awk '{print $5,$9}'
       9 }
      10
      11
      12 nr=0          #  We want the while loop to be able to manipulate these and
      13 totalSize=0   #+ to be able to see the changes after the 'while' finished.
      14
      15 while read fileSize fileName ; do
      16   echo "$fileName is $fileSize bytes"
      17   let nr++
      18   totalSize=$((totalSize+fileSize))   # Or: "let totalSize+=fileSize"
      19 done<<EOF
      20 $(doesOutput)
      21 EOF
      22
      23 echo "$nr files totaling $totalSize bytes"

Prev | Home | Next
I/O Redirection | Up | Applications