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 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
.[161]
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.[162]
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.[163]
(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.
In addition to file streams, Common Lisp supports other kinds of streams, which can also be used with the various reading, writing, and printing I/O functions. For instance, you can read data from, or write data to, a string using STRING-STREAM
s, which you can create with the functions MAKE-STRING-INPUT-STREAM
and MAKE-STRING-OUTPUT- STREAM
.
MAKE-STRING-INPUT-STREAM
takes a string and optional start and end indices to bound the area of the string from which data should be read and returns a character stream that you can pass to any of the character-based input functions such as READ-CHAR
, READ-LINE
, or READ
. For example, if you have a string containing a floating-point literal in Common Lisp's syntax, you can convert it to a float like this:
(let ((s (make-string-input-stream '1.23')))
(unwind-protect (read s)
(close s)))
Similarly, MAKE-STRING-OUTPUT-STREAM
creates a stream you can use with FORMAT
, PRINT
, WRITE-CHAR
, WRITE-LINE
, and so on. It takes no arguments. Whatever you write, a string output stream will be accumulated into a string that can then be obtained with the function GET-OUTPUT-STREAM-STRING
. Each time you call GET-OUTPUT-STREAM-STRING
, the stream's internal string is cleared so you can reuse an existing string output stream.
However, you'll rarely use these functions directly, because the macros WITH-INPUT-FROM- STRING
and WITH-OUTPUT-TO-STRING
provide a more convenient interface. WITH-INPUT-FROM-STRING
is similar to WITH-OPEN-FILE
—it creates a string input stream from a given string and then executes the forms in its body with the stream bound to the variable you provide. For instance, instead of the LET
form with the explicit UNWIND- PROTECT
, you'd probably write this:
(with-input-from-string (s '1.23')
(read s))
The WITH-OUTPUT-TO-STRING
macro is similar: it binds a newly created string output stream to a variable you name and then executes its body. After all the body forms have been executed, WITH-OUTPUT-TO-STRING
returns the value that would be returned by GET-OUTPUT-STREAM-STRING
.
CL-USER> (with-output-to-string (out)
(format out 'hello, world ')
(format out '~s' (list 1 2 3)))
'hello, world (1 2 3)'
The other kinds of streams defined in the language standard provide various kinds of stream 'plumbing,' allowing you to plug together streams in almost any configuration. A BROADCAST-