| Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
|---|---|---|
| Prev | Chapter 16. I/O Redirection | Next | 
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 23-7). The < operator at the end of the code block accomplishes this.
Example 16-4. Redirected while 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 #+ Filename=${1:-names.data}
  10 #  can replace the above test (parameter substitution).
  11 
  12 count=0
  13 
  14 echo
  15 
  16 while [ "$name" != Smith ]  # Why is variable $name in quotes?
  17 do
  18   read name                 # Reads from $Filename, rather than stdin.
  19   echo $name
  20   let "count += 1"
  21 done <"$Filename"           # Redirects stdin to file $Filename. 
  22 #    ^^^^^^^^^^^^
  23 
  24 echo; echo "$count names read"; echo
  25 
  26 #  Note that in some older shell scripting languages,
  27 #+ the redirected loop would run as a subshell.
  28 # Therefore, $count would return 0, the initialized value outside the loop.
  29 #  Bash and ksh avoid starting a subshell whenever possible,
  30 # +so that this script, for example, runs correctly.
  31 #
  32 # Thanks to Heiner Steven for pointing this out.
  33 
  34 exit 0 | 
Example 16-5. 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 <"$Filename" # Loop reads from file $Filename. 32 # ^^^^^^^^^^^^ 33 34 35 exec 0<&3 # Restore old stdin. 36 exec 3<&- # Close temporary fd 3. 37 38 echo; echo "$count names read"; echo 39 40 exit 0  | 
Example 16-6. 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 16-7. 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 < "$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 16-8. 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 16-9. 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 16-10. Data file "names.data" for above examples
1 Aristotle 2 Belisarius 3 Capablanca 4 Euler 5 Goethe 6 Hamurabi 7 Jonah 8 Laplace 9 Maroczy 10 Purcell 11 Schmidt 12 Semmelweiss 13 Smith 14 Turing 15 Venn 16 Wilson 17 Znosko-Borowski 18 19 # This is a data file for 20 #+ "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.