LISP - File I/O
Lisp - Modern File I/O
Section titled “Lisp - Modern File I/O”File input/output (I/O) in Common Lisp is handled through an abstraction called streams. A stream is a source or sink of data, which can be a file, the terminal, a network connection, or even a string. The same functions for reading and writing data, like format and read-line, can work on any type of stream.
This chapter covers the modern, safe, and idiomatic way to perform file operations in Common Lisp.
The Right Way: with-open-file
Section titled “The Right Way: with-open-file”While Lisp provides a low-level open function, the recommended best practice is to always use the with-open-file macro. It automates the crucial step of closing the file, even if an error occurs during processing. This prevents resource leaks and is a core principle of robust programming.
The syntax for with-open-file is:
(with-open-file (stream-variable filename-or-path &key options...) ;; Code to read from or write to the stream-variable goes here ...);; The file is automatically closed hereKey options for with-open-file include:
- :direction: Specifies the stream’s purpose. Common values are:
*
:input(default): For reading from a file. *:output: For writing to a file. *:io: For both reading and writing. - :if-exists: What to do if
:directionis:outputand the file already exists. *:supersede(default): Overwrite the existing file. *:append: Add new content to the end of the file. *:error: Signal an error. - :if-does-not-exist: What to do if the file doesn’t exist.
*
:create(default for:output): Create a new file. *:error(default for:input): Signal an error. *nil: Returnnilfromwith-open-fileinstead of signaling an error. - :element-type: The type of data being transferred. Defaults to
characterfor text files. For binary files, use(unsigned-byte 8). - :external-format: The character encoding. It’s good practice to specify this explicitly. A modern standard is
:utf-8.
Writing to a File
Section titled “Writing to a File”This example writes several lines of text to a file named notes.txt. If the file exists, it will be overwritten.
Example
Section titled “Example”(defun write-notes () (with-open-file (stream "notes.txt" :direction :output :if-exists :supersede :external-format :utf-8) (format stream "Welcome to a modern Lisp tutorial!~%") (format stream "File I/O is managed safely with with-open-file.~%") (format stream "The file stream is automatically closed.~%")) (format t "File 'notes.txt' has been written successfully.~%"))
(write-notes)After running this code, a file named notes.txt will be created in the same directory with the specified content. The ~% in format is a convenient way to print a newline character.
Reading from a File
Section titled “Reading from a File”Now, let’s read the content back from notes.txt. We will read the file line-by-line until we reach the end.
Example
Section titled “Example”(defun read-notes () (with-open-file (stream "notes.txt" :if-does-not-exist nil) (if stream (loop for line = (read-line stream nil) while line do (format t "Read line: ~a~%" line)) (format t "File 'notes.txt' does not exist.~%"))))
(read-notes)In this example:
:if-does-not-exist nilprevents an error if the file is missing;with-open-filewill simply returnnil.(read-line stream nil)reads one line of text. The second argument,nil, tells it to returnnilat the end of the file instead of signaling an error. Thisnilthen causes theloop’swhilecondition to fail, gracefully terminating the loop.
Output
Section titled “Output”If you run this after the writing example, the output will be:
Read line: Welcome to a modern Lisp tutorial!Read line: File I/O is managed safely with with-open-file.Read line: The file stream is automatically closed.A Note on Pathnames
Section titled “A Note on Pathnames”Common Lisp has a sophisticated, platform-independent way to handle file paths called pathnames. While strings like "notes.txt" often work, using the pathname constructor #p can be more robust, especially for complex paths.
;; A more robust way to specify the file path(with-open-file (stream #p"notes.txt" :direction :input) ...)Using pathnames allows Lisp to handle directory separators and other platform-specific details automatically.