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:

  1. (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:

  1. (let ((in (open "/some/file/name.txt")))
  2. (format t "~a~%" (read-line in))
  3. (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.

  1. (let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
  2. (when in
  3. (format t "~a~%" (read-line in))
  4. (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:

  1. (let ((in (open "/some/file/name.txt" :if-does-not-exist nil)))
  2. (when in
  3. (loop for line = (read-line in nil)
  4. while line do (format t "~a~%" line))
  5. (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. (1 2 3)
  2. 456
  3. "a string" ; this is a comment
  4. ((a b)
  5. (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:

  1. CL-USER> (defparameter *s* (open "/some/file/name.txt"))
  2. *S*
  3. CL-USER> (read *s*)
  4. (1 2 3)
  5. CL-USER> (read *s*)
  6. 456
  7. CL-USER> (read *s*)
  8. "a string"
  9. CL-USER> (read *s*)
  10. ((A B) (C D))
  11. CL-USER> (close *s*)
  12. 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