45.22 Handling Files Line-by-Line
It isn't easy to see how to read a file line-by-line in a shell script.
And while you
can write a file line-by-line by using the file-appending operator
The trick is to open the file and associate a file descriptor number (3, 4, ..., 9) with it. UNIX keeps a file pointer , like a bookmark in a book, that tells it where the next read or write should be in each open file. For example, if you open a file for reading and read the first line, the file pointer will stay at the start of the second line. The next read from that same open file will move the pointer to the start of the third line. This trick only works with files that stay open; each time you open a file, the file pointer is set to the start of the file. [1] The Bourne shell exec command ( 45.7 ) can open a file and associate a file descriptor with it. For example, this exec command makes the standard input of all following commands come from the file formfile :
...all commands read their stdin from default place exec < formfile ...all commands will read their stdin from formfile There's another way to rearrange file descriptors: by doing it at the last line of while loops, if and case statements. For example, all commands in the while loop below will take their standard inputs from the file formfile . The standard input outside the while loop isn't changed:
...all commands read their stdin from default place while ... do ...all commands will read their stdin from formfile done < formfile ...all commands read their stdin from default place I call those "redirected-I/O loops." Those and other Bourne shell structures have some problems ( 45.23 ) , but they're usually worth the work to solve. We'll use all that to make a shell script for filling in forms. The script, formprog , reads an empty form file like this one, line by line:
Name: Address: City: State/Province: Phone: FAX: Project: Corporate Decision Comments:
If a line has just a label, like
Project: Corporate Decision the script doesn't prompt you; it just writes the line to the output file:
% Here's the formprog script. The line numbers are for reference only; don't type them into the file. There's more explanation after the script:
1 #!/bin/sh 2 # formprog - fill in template form from $1, leave completed form in $2 3 # TABSTOPS ARE SET AT 4 IN THIS SCRIPT 4 5 template="$1" completed="$2" errors=/tmp/formprog$$ 6 myname=`basename $0` # BASENAME OF THIS SCRIPT (NO LEADING PATH) 7 trap 'rm -f $errors; exit' 0 1 2 15 8 9 # READ $template LINE-BY-LINE, WRITE COMPLETED LINES TO $completed: 10 exec 4<&0 # SAVE ORIGINAL stdin (USUALLY TTY) AS FD 4 11 while read label text 12 do 13 case "$label" in 14 ?*:) # FIRST WORD ENDS WITH A COLON; LINE IS OKAY 15 case "$text" in 16 ?*) # SHOW LINE ON SCREEN AND PUT INTO completed FILE: 17 echo "$label $text" 18 echo "$label $text" 1>&3 19 ;; 20 *) # FILL IT IN OURSELVES: 21 echo -n "$label " 22 exec 5<&0 # SAVE template FILE FD; DO NOT CLOSE! 23 exec 0<&4 # RESTORE ORIGINAL stdin TO READ ans 24 read ans 25 exec 0<&5 # RECONNECT template FILE TO stdin 26 case "$ans" in 27 "") ;; # EMPTY; DO NOTHING 28 *) echo "$label $ans" 1>&3 ;; 29 esac 30 ;; 31 esac 32 ;; 33 *) echo "$myname: bad $1 line: '$label $text'" 1>&2; break;; 34 esac 35 done <"$template" 2>$errors 3>"$completed" 36 37 if [ -s $errors ]; then 38 /bin/cat $errors 1>&2 39 echo "$myname: should you remove '$completed' file?" 1>&2 40 fi
Line 10
uses the
During
lines 11-35
of the redirected-I/O
while
loop:
all commands' standard input comes from the file named in Here's what happens each time the loop is executed:
The loop keeps reading and writing line by line until the
read
at the top of the loop reaches the end-of-file of - |
|