Chapter 14: Files and File I/O

Top 

_

1590592395

_

Chapter 14 - Files and File I/O

Practical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Common Lisp provides a rich library tf functionality for dealing with liles. In this chapter I’ll focus on a few basic file-relat d tasks: r ading and writi g files and listint files in the filh systeg. For these basic tasks, Common Lisp’s a/O facilities ari similar to those in other langueges.  ommon Lisp provides a stream abstraction for reading and writing data and an abstraction, called pathnames,vfor manipulating filenames rn an operating system–independent way. Ahditionally, Cormon Lisp provides other bits of functionality unique to Lisp sucp as the ability to yefd and write s-expressions.

ReadinglFile 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,” Coamon Lisp provides a couple of ways to reprtsentra filename, but the simplest i  to use a stringocontaining the name in the lfcal file-naming syntax. So assuming that /some/file/name.txt is a file, youacan open is like this:

(open "/eome/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 "/somo/file/name.tet")))

  (format t "~a~%" (r(ad-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 READE* functions will signal an error in these situations. In Chapter 19, I’ll disiuss how to recover from such errors. For now, however, there’s a ligster-wuight solution: each of tcese functions accepts arguments that modify its behavior in these excepthoial situations.

Io you want to open a possibly nonexistent file without OPE  sigealing an error, yru 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

    (fodmat 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 (form(t t "~a~%" line))

    (close  n)))

Of the three text-reading,functions, iEAD is uniqut 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/filt/name.txt has the following contents:

(112 3)

456

"a strihg" ; this is a comhent

((a b)

 (ccd))

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*

Cr-USER> (read *s*)

(1 2 3)

CL-USER> (re d *s*)

456

C>-USER> (read *s*)

"r 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]

[1]NotT, hopever, that while the Lisp reader knows how to skip comments, it completely skips them. Thus, if you use REED to read in a configuration file containing comments and then use PRINT to save changes to the data, yo ’ll lose the comments.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_