Interacting with the File System
While the most common interaction with the file system is probably **OPEN**
ing files for reading and writing, you’ll also occasionally want to test whether a file exists, list the contents of a directory, delete and rename files, create directories, and get information about a file such as who owns it, when it was last modified, and its length. This is where the generality of the pathname abstraction begins to cause a bit of pain: because the language standard doesn’t specify how functions that interact with the file system map to any specific file system, implementers are left with a fair bit of leeway.
That said, most of the functions that interact with the file system are still pretty straightforward. I’ll discuss the standard functions here and point out the ones that suffer from nonportability between implementations. In the next chapter you’ll develop a pathname portability library to smooth over some of those nonportability issues.
To test whether a file exists in the file system corresponding to a pathname designator—a pathname, namestring, or file stream—you can use the function **PROBE-FILE**
. If the file named by the pathname designator exists, **PROBE-FILE**
returns the file’s truename, a pathname with any file system-level translations such as resolving symbolic links performed. Otherwise, it returns **NIL**
. However, not all implementations support using this function to test whether a directory exists. Also, Common Lisp doesn’t provide a portable way to test whether a given file that exists is a regular file or a directory. In the next chapter you’ll wrap **PROBE-FILE**
with a new function, file-exists-p
, that can both test whether a directory exists and tell you whether a given name is the name of a file or directory.
Similarly, the standard function for listing files in the file system, **DIRECTORY**
, works fine for simple cases, but the differences between implementations make it tricky to use portably. In the next chapter you’ll define a list-directory
function that smoothes over some of these differences.
**DELETE-FILE**
and **RENAME-FILE**
do what their names suggest. **DELETE-FILE**
takes a pathname designator and deletes the named file, returning true if it succeeds. Otherwise it signals a **FILE-ERROR**
.11
**RENAME-FILE**
takes two pathname designators and renames the file named by the first name to the second name.
You can create directories with the function **ENSURE-DIRECTORIES-EXIST**
. It takes a pathname designator and ensures that all the elements of the directory component exist and are directories, creating them as necessary. It returns the pathname it was passed, which makes it convenient to use inline.
(with-open-file (out (ensure-directories-exist name) :direction :output)
...
)
Note that if you pass **ENSURE-DIRECTORIES-EXIST**
a directory name, it should be in directory form, or the leaf directory won’t be created.
The functions **FILE-WRITE-DATE**
and **FILE-AUTHOR**
both take a pathname designator. **FILE-WRITE-DATE**
returns the time in number of seconds since midnight January 1, 1900, Greenwich mean time (GMT), that the file was last written, and **FILE-AUTHOR**
returns, on Unix and Windows, the file owner.12
To find the length of a file, you can use the function **FILE-LENGTH**
. For historical reasons **FILE-LENGTH**
takes a stream as an argument rather than a pathname. In theory this allows **FILE-LENGTH**
to return the length in terms of the element type of the stream. However, since on most present-day operating systems, the only information available about the length of a file, short of actually reading the whole file to measure it, is its length in bytes, that’s what most implementations return, even when **FILE-LENGTH**
is passed a character stream. However, the standard doesn’t require this behavior, so for predictable results, the best way to get the length of a file is to use a binary stream.13
(with-open-file (in filename :element-type '(unsigned-byte 8))
(file-length in))
A related function that also takes an open file stream as its argument is **FILE-POSITION**
. When called with just a stream, this function returns the current position in the file—the number of elements that have been read from or written to the stream. When called with two arguments, the stream and a position designator, it sets the position of the stream to the designated position. The position designator must be the keyword :start
, the keyword :end
, or a non-negative integer. The two keywords set the position of the stream to the start or end of the file while an integer moves to the indicated position in the file. With a binary stream the position is simply a byte offset into the file. However, for character streams things are a bit more complicated because of character-encoding issues. Your best bet, if you need to jump around within a file of textual data, is to only ever pass, as a second argument to the two-argument version of **FILE-POSITION**
, a value previously returned by the one-argument version of **FILE-POSITION**
with the same stream argument.