(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 /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.[151]
By default OPEN
returns character streams, which translate the underlying bytes to characters according to a particular character-encoding scheme.[152] To read the raw bytes, you need to pass OPEN
an :element- type
argument of '(unsigned-byte 8)
.[153] You can pass the resulting stream to the function READ-BYTE
, which will return an integer between 0 and 255 each time it's called. READ-BYTE
, like the character-reading functions, also accepts optional arguments to specify whether it should signal an error if called at the end of the file and what value to return if not. In Chapter 24 you'll build a library that allows you to conveniently read structured binary data using READ-BYTE
.[154]
One last reading function, READ-SEQUENCE
, works with both character and binary streams. You pass it a sequence (typically a vector) and a stream, and it attempts to fill the sequence with data from the stream. It returns the index of the first element of the sequence that wasn't filled or the length of the sequence if it was able to completely fill it. You can also pass :start
and :end
keyword arguments to specify a subsequence that should be filled instead. The sequence argument must be a type that can hold elements of the stream's element type. Since most operating systems support some form of block I/O, READ-SEQUENCE
is likely to be quite a bit more efficient than filling a sequence by repeatedly calling READ-BYTE
or READ-CHAR
.
To write data to a file, you need an output stream, which you obtain by calling OPEN
with a :direction
keyword argument of :output
. When opening a file for output, OPEN
assumes the file shouldn't already exist and will signal an error if it does. However, you can change that behavior with the :if-exists
keyword argument. Passing the value :supersede
tells OPEN
to replace the existing file. Passing :append
causes OPEN
to open the existing file such that new data will be written at the end of the file, while :overwrite
returns a stream that will overwrite existing data starting from the beginning of the file. And passing NIL
will cause OPEN
to return NIL
instead of a stream if the file already exists. A typical use of OPEN
for output looks like this:
(open '/some/file/name.txt' :direction :output :if-exists :supersede)
Common Lisp also provides several functions for writing data: WRITE- CHAR
writes a single character to the stream. WRITE-LINE
writes a string followed by a newline, which will be output as the appropriate end-of-line character or characters for the platform. Another function, WRITE-STRING
, writes a string without adding any end-of-line characters. Two different functions can print just a newline: TERPRI
—short for 'terminate print'—unconditionally prints a newline character, and FRESH-LINE
prints a newline character unless the stream is at the beginning of a line. FRESH-LINE
is handy when you want to avoid spurious blank lines in textual output generated by different functions called in sequence. For example, suppose you have one function that generates output that should always be followed by a line break and another that should start on a new line. But assume that if the functions are called one after the other, you don't want a blank line between the two bits of output. If you use FRESH-LINE
at the beginning of the second function, its output will always start on a new line, but if it's called right after the first, it won't emit an extra line break.
Several functions output Lisp data as s-expressions: PRINT
prints an s- expression preceded by an end-of-line and followed by a space. PRIN1
prints just the s-expression. And the function PPRINT
prints s-expressions like PRINT
and PRIN1
but using the 'pretty printer,' which tries to print its output in an aesthetically pleasing way.