Blocks of code, such as 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 TODO Example 24-11). The <
operator at the end
of the code block accomplishes this.
Redirected while loop
#!/bin/bash # redir2.sh
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi #+ Filename=${1:-names.data} # can replace the above test (parameter substitution).
count=0
echo
while [ "$name" != Smith ] # Why is variable $name in quotes? do read name # Reads from $Filename, rather than stdin. echo $name let "count += 1" done <"$Filename" # Redirects stdin to file $Filename. # ^^^^^^^^^^^^
echo; echo "$count names read"; echo
exit 0
# Note that in some older shell scripting languages, #+ the redirected loop would run as a subshell. # Therefore, $count would return 0, the initialized value outside the loop. # Bash and ksh avoid starting a subshell *whenever possible*, #+ so that this script, for example, runs correctly. # (Thanks to Heiner Steven for pointing this out.)
# However . . . # Bash *can* sometimes start a subshell in a PIPED "while-read" loop, #+ as distinct from a REDIRECTED "while" loop.
abc=hi echo -e "1\n2\n3" | while read l do abc="$l" echo $abc done echo $abc
# Thanks, Bruno de Oliveira Schneider, for demonstrating this #+ with the above snippet of code. # And, thanks, Brian Onn, for correcting an annotation error.
Alternate form of redirected while loop
#!/bin/bash
# This is an alternate form of the preceding script.
# Suggested by Heiner Steven #+ as a workaround in those situations when a redirect loop #+ runs as a subshell, and therefore variables inside the loop # +do not keep their values upon loop termination.
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi
exec 3<&0 # Save stdin to file descriptor 3. exec 0<"$Filename" # Redirect standard input.
count=0 echo
while [ "$name" != Smith ] do read name # Reads from redirected stdin ($Filename). echo $name let "count += 1" done # Loop reads from file $Filename #+ because of line 20.
# The original version of this script terminated the "while" loop with #+ done <"$Filename" # Exercise: # Why is this unnecessary?
exec 0<&3 # Restore old stdin. exec 3<&- # Close temporary fd 3.
echo; echo "$count names read"; echo
exit 0
Redirected until loop
#!/bin/bash # Same as previous example, but with "until" loop.
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi
# while [ "$name" != Smith ] until [ "$name" = Smith ] # Change != to =. do read name # Reads from $Filename, rather than stdin. echo $name done <"$Filename" # Redirects stdin to file $Filename. # ^^^^^^^^^^^^
# Same results as with "while" loop in previous example.
exit 0
Redirected for loop
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi
line_count=`wc $Filename | awk '{ print $1 }'` # Number of lines in target file. # # Very contrived and kludgy, nevertheless shows that #+ it's possible to redirect stdin within a "for" loop... #+ if you're clever enough. # # More concise is line_count=$(wc -l < "$Filename")
for name in `seq $line_count` # Recall that "seq" prints sequence of numbers. # while [ "$name" != Smith ] -- more complicated than a "while" loop -- do read name # Reads from $Filename, rather than stdin. echo $name if [ "$name" = Smith ] # Need all this extra baggage here. then break fi done <"$Filename" # Redirects stdin to file $Filename. # ^^^^^^^^^^^^
exit 0
Redirected for loop (both stdin and stdout redirected)
We can modify the previous example to also redirect the output of the loop.
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi
Savefile=$Filename.new # Filename to save results in. FinalName=Jonah # Name to terminate "read" on.
line_count=`wc $Filename | awk '{ print $1 }'` # Number of lines in target file.
for name in `seq $line_count` do read name echo "$name" if [ "$name" = "$FinalName" ] then break fi done < "$Filename" > "$Savefile" # Redirects stdin to file $Filename, # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ and saves it to backup file.
exit 0
Redirected if/then test
#!/bin/bash
if [ -z "$1" ] then Filename=names.data # Default, if no filename specified. else Filename=$1 fi
TRUE=1
if [ "$TRUE" ] # if true and if : also work. then read name echo $name fi <"$Filename" # ^^^^^^^^^^^^
# Reads only first line of file. # An "if/then" test has no way of iterating unless embedded in a loop.
exit 0
Data file names.data for above examples
Aristotle Arrhenius Belisarius Capablanca Dickens Euler Goethe Hegel Jonah Laplace Maroczy Purcell Schmidt Schopenhauer Semmelweiss Smith Steinmetz Tukhashevsky Turing Venn Warshawski Znosko-Borowski
# This is a data file for #+ "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 TODO 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.
# This example by Albert Siersema # Used with permission (thanks!).
function doesOutput() # Could be an external command too, of course. # Here we show you can use a function as well. { ls -al *.jpg | awk '{print $5,$9}' }
nr=0 # We want the while loop to be able to manipulate these and totalSize=0 #+ to be able to see the changes after the 'while' finished.
while read fileSize fileName ; do echo "$fileName is $fileSize bytes" let nr++ totalSize=$((totalSize+fileSize)) # Or: "let totalSize+=fileSize" done<<EOF $(doesOutput) EOF
echo "$nr files totaling $totalSize bytes"