All input and output in Common LISP is handled through a special type of object called a stream. When a stream is not specified, Common LISP's default behavior is to send output and receive input from a stream bound to the constant *terminal-io* corresponding to the computer's keyboard and monitor. Evaluating *terminal-io* shows a printed representation of this stream:
>*terminal-io* #<two-way stream 0016c7a8>
The designation ``two-way'' here specifies that it is a stream that is capable of handling both input and output.
In all the examples so far, printing and reading has been done without specifying a stream, hence the effects have been to interact with the keyboard and screen. However, print, format, read and the other functions mentioned in this chapter allow optional specification of a different stream for input or output.
In order to receive input from or send output to a file, the file must be attached to an appropriate stream. The easiest way to handle this is with the macro with-open-file. This is the general form for with-open-file:
(with-open-file (<stream> <filename>) <body>)
Unless specified otherwise, with-open-file assumes that the stream is an input stream, in other words, that you will be reading data from the named file.
Suppose you have a file called ``foo'', which looks like this:
Here's how to get the first LISP expression from foo.this is an example of a file (that has things) we might want (to read in) 4 5 67
A slightly more complicated operation is to make a list of all the expressions in foo. The following will work:>(with-open-file (infile "foo") (read infile)) THIS
(See the appendix entry for `do' if you do not know how it works.)(with-open-file (infile "foo") (do ((result nil (cons next result)) (next (read infile nil 'eof) (read infile nil 'eof))) ((equal next 'eof) (reverse result))))
If you evaluate this code it will return the list:
At the beginning of the do, two variables are specified -- result and next. The initial value of result is nil. The intial value of next is the result of evaluating (read infile nil 'eof). The effects of the last two arguments in this read are explained below.(THIS IS AN EXAMPLE OF A FILE (THAT HAS THINGS) WE MIGHT WANT (TO READ IN) 4 5 67)
As soon as the variables are initialized, the test (equal next 'eof) is performed. If it is true then (reverse result) is evaluated and returned. If not, then all expressions in the body of the do are evaluated (in this case there are none).
After the body has been evaluated, all the variables are updated with the expressions indicated. In other words, result gets next cons-ed into it, and next gets updated with a new read from instream.
The second and third argments to read control its behavior when it reaches the end of the file. The second argument, nil in this case, indicates that reaching the end of the file should not generate an error. The third argument, in this case the symbol eof, indicates what should be returned instead of an error. This enables the do loop to determine when the end of file has been reached and return the appropriate result. Notice that choosing eof as the result to return would cause the do loop to stop if it reads the symbol eof in the middle of a file. This may or may not be desirable behavior.
Writing to a file is very similar to reading.
And the file now contains one line:>(with-open-file (outfile "foo" :direction :output) (prin1 '(here is an example) outfile)) (HERE IS AN EXAMPLE)
Note that it is necessary to specify the :direction as :output. With-open-file assumes that a file is being opened for input by default, so this must be explicity overridden when doing file output. Files may also be opened as :direction :io, which allows input and output.(HERE IS AN EXAMPLE)
Notice also that this example destroyed the previous contents of foo. This behavior can be controlled with the :if-exists option. For example:
This will add a second line to the file foo, so that it contains>(with-open-file (outfile "foo" :direction :output :if-exists :append) (print '(here is a second list) outfile)) (HERE IS A SECOND LIST)
With-open-file will produce an error if the file foo does not already exist, unless its behavior is controlled using the :if-does-not-exist :create or :if-does-not-exist nil options. The first of these options creates a file with the specified name, the second causes the body of the with-open-file to be ignored, and the value nil is returned.(HERE IS AN EXAMPLE) (HERE IS A SECOND LIST)
Format can be used with the name of a stream as its first argument.
>(with-open-file (outfile "foo" :direction :output) (format outfile "~%This is text.~%")) NIL
This overwrites foo so that it contains three lines -- a blank line, a line with
and another blank line.This is text.
Print, prin1, princ, terpri, format, read and read-line all can be used with stream specifications to do file input and output.
© Colin Allen & Maneesh Dhagat