The dominant file systems at the time the design [of Common Lisp] was done were TOPS-10, TENEX, TOPS- 20, VAX VMS, AT&T Unix, MIT Multics, MIT ITS, not to mention a bunch of mainframe [OSs]. Some were uppercase only, some mixed, some were case-sensitive but case- translating (like CL). Some had dirs as files, some not. Some had quote chars for funny file chars, some not. Some had wildcards, some didn't. Some had :up in relative pathnames, some didn't. Some had namable root dirs, some didn't. There were file systems with no directories, file systems with non-hierarchical directories, file systems with no file types, file systems with no versions, file systems with no devices, and so on. If you look at the pathname abstraction from the point of view of any single file system, it seems baroque. However, if you take even two such similar file systems as Windows and Unix, you can already begin to see differences the pathname system can help abstract away—Windows filenames contain a drive letter, for instance, while Unix filenames don't. The other advantage of having the pathname abstraction designed to handle the wide variety of file systems that existed in the past is that it's more likely to be able to handle file systems that may exist in the future. If, say, versioning file systems come back into vogue, Common Lisp will be ready. |
A pathname is a structured object that represents a filename using six components: host, device, directory, name, type, and version. Most of these components take on atomic values, usually strings; only the directory component is further structured, containing a list of directory names (as strings) prefaced with the keyword :absolute
or :relative
. However, not all pathname components are needed on all platforms—this is one of the reasons pathnames strike many new Lispers as gratuitously complex. On the other hand, you don't really need to worry about which components may or may not be used to represent names on a particular file system unless you need to create a new pathname object from scratch, which you'll almost never need to do. Instead, you'll usually get hold of pathname objects either by letting the implementation parse a file system-specific namestring into a pathname object or by creating a new pathname that takes most of its components from an existing pathname.
For instance, to translate a namestring to a pathname, you use the PATHNAME
function. It takes a pathname designator and returns an equivalent pathname object. When the designator is already a pathname, it's simply returned. When it's a stream, the original filename is extracted and returned. When the designator is a namestring, however, it's parsed according to the local filename syntax. The language standard, as a platform-neutral document, doesn't specify any particular mapping from namestring to pathname, but most implementations follow the same conventions on a given operating system.
On Unix file systems, only the directory, name, and type components are typically used. On Windows, one more component—usually the device or host—holds the drive letter. On these platforms, a namestring is parsed by first splitting it into elements on the path separator—a slash on Unix and a slash or backslash on Windows. The drive letter on Windows will be placed into either the device or the host component. All but the last of the other name elements are placed in a list starting with :absolute
or :relative
depending on whether the name (ignoring the drive letter, if any) began with a path separator. This list becomes the directory component of the pathname. The last element is then split on the rightmost dot, if any, and the two parts put into the name and type components of the pathname.[157]
You can examine these individual components of a pathname with the functions PATHNAME-DIRECTORY
, PATHNAME- NAME
, and PATHNAME-TYPE
.
(pathname-directory (pathname '/foo/bar/baz.txt')) ==> (:ABSOLUTE 'foo' 'bar')
(pathname-name (pathname '/foo/bar/baz.txt')) ==> 'baz'
(pathname-type (pathname '/foo/bar/baz.txt')) ==> 'txt'
Three other functions—PATHNAME-HOST
, PATHNAME- DEVICE
, and PATHNAME-VERSION
—allow you to get at the other three pathname components, though they're unlikely to have interesting values on Unix. On Windows either PATHNAME-HOST
or PATHNAME-DEVICE
will return the drive letter.
Like many other built-in objects, pathnames have their own read syntax, #p
followed by a double-quoted string. This allows you to print and read back s-expressions containing pathname objects, but because the syntax depends on the namestring parsing algorithm, such data isn't necessarily portable between operating systems.
(pathname '/foo/bar/baz.txt') ==> #p'/foo/bar/baz.txt'
To translate a pathname back to a namestring—for instance, to present to the user—you can use the function NAMESTRING
, which takes a pathname designator and returns a namestring. Two other functions, DIRECTORY-NAMESTRING
and FILE- NAMESTRING
, return a partial namestring. DIRECTORY- NAMESTRING
combines the elements of the directory component into a local directory name, and FILE-NAMESTRING
combines the name and type components.[158]
(namestring #p'/foo/bar/baz.txt') ==> '/foo/bar/baz.txt'
(directory-namestring #p'/foo/bar/baz.txt') ==> '/foo/bar/'
(file-namestring #p'/foo/bar/baz.txt') ==> 'baz.txt'
You can construct arbitrary pathnames using the MAKE-PATHNAME
function. It takes one keyword argument for each pathname component and returns a pathname with any supplied components filled in and the rest NIL
.[159]
(make-pathname
:directory '(:absolute 'foo' 'bar')
:name 'baz'
:type 'txt') ==> #p'/foo/bar/baz.txt'
However, if you want your programs to be portable, you probably don't want to make pathnames completely from scratch: even though the pathname abstraction protects you from unportable filename syntax, filenames can be unportable in other ways. For instance, the filename /home/peter/foo.txt
is no good on an OS X box where /home/
is called /Users/
.
Another reason not to make pathnames completely from scratch is that different implementations use the pathname components slightly differently. For instance, as mentioned previously, some Windows-based Lisp implementations store the drive letter in the device component while others store it in the host component. If you write code like this:
(make-pathname :device 'c' :directory '(:absolute 'foo' 'bar') :name 'baz')
it will be correct on some implementations but not on others.
Rather than making names from scratch, you can build a new pathname based on an existing pathname with MAKE-PATHNAME
's keyword parameter :defaults
. With this parameter you can provide a pathname designator, which will supply the values for any components not specified