| 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.