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-11). The < operator at the end of the code block accomplishes this.
Example 16-5. 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-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 16-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 16-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 16-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 16-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 16-11. 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.