Reading File Data
The most basic file I/O task is to read the contents of a file. You obtain a stream from which you can read a file’s contents with the **OPEN**
function. By default **OPEN**
returns a character-based input stream you can pass to a variety of functions that read one or more characters of text: **READ-CHAR**
reads a single character; **READ-LINE**
reads a line of text, returning it as a string with the end-of-line character(s) removed; and **READ**
reads a single s-expression, returning a Lisp object. When you’re done with the stream, you can close it with the **CLOSE**
function.
The only required argument to **OPEN**
is the name of the file to read. As you’ll see in the section “Filenames,” Common Lisp provides a couple of ways to represent a filename, but the simplest is to use a string containing the name in the local file-naming syntax. So assuming that /some/file/name.txt
is a file, you can open it like this:
(open "/some/file/name.txt")
You can use the object returned as the first argument to any of the read functions. For instance, to print the first line of the file, you can combine **OPEN**
, **READ-LINE**
, and **CLOSE**
as follows:
(let ((in (open "/some/file/name.txt")))
(format t "~a~%" (read-line in))
(close in))
Of course, a number of things can go wrong while trying to open and read from a file. The file may not exist. Or you may unexpectedly hit the end of the file while reading. By default **OPEN**
and the READ-*
functions will signal an error in these situations. In Chapter 19, I’ll discuss how to recover from such errors. For now, however, there’s a lighter-weight solution: each of these functions accepts arguments that modify its behavior in these exceptional situations.
If you want to open a possibly nonexistent file without **OPEN**
signaling an error, you can use the keyword argument :if-does-not-exist
to specify a different behavior. The three possible values are :error
, the default; :create
, which tells it to go ahead and create the file and then proceed as if it had already existed; and **NIL**
, which tells it to return **NIL**
instead of a stream. Thus, you can change the previous example to deal with the possibility that the file may not exist.
(let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
(when in
(format t "~a~%" (read-line in))
(close in)))
The reading functions—**READ-CHAR**
, **READ-LINE**
, and **READ**
--all take an optional argument, which defaults to true, that specifies whether they should signal an error if they’re called at the end of the file. If that argument is **NIL**
, they instead return the value of their third argument, which defaults to **NIL**
. Thus, you could print all the lines in a file like this:
(let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
(when in
(loop for line = (read-line in nil)
while line do (format t "~a~%" line))
(close in)))
Of the three text-reading functions, **READ**
is unique to Lisp. This is the same function that provides the R in the REPL and that’s used to read Lisp source code. Each time it’s called, it reads a single s-expression, skipping whitespace and comments, and returns the Lisp object denoted by the s-expression. For instance, suppose /some/file/name.txt
has the following contents:
(1 2 3)
456
"a string" ; this is a comment
((a b)
(c d))
In other words, it contains four s-expressions: a list of numbers, a number, a string, and a list of lists. You can read those expressions like this:
CL-USER> (defparameter *s* (open "/some/file/name.txt"))
*S*
CL-USER> (read *s*)
(1 2 3)
CL-USER> (read *s*)
456
CL-USER> (read *s*)
"a string"
CL-USER> (read *s*)
((A B) (C D))
CL-USER> (close *s*)
T
As you saw in Chapter 3, you can use **PRINT**
to print Lisp objects in “readable” form. Thus, whenever you need to store a bit of data in a file, **PRINT**
and **READ**
provide an easy way to do it without having to design a data format or write a parser. They even—as the previous example demonstrated—give you comments for free. And because s-expressions were designed to be human editable, it’s also a fine format for things like configuration files.1