| Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
|---|---|---|
| Prev | Chapter 20. 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 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" |