by other arguments. For example, the following expression creates a pathname with an .html
extension and all other components the same as the pathname in the variable input-file
:
(make-pathname :type 'html' :defaults input-file)
Assuming the value in input-file
was a user-provided name, this code will be robust in the face of operating system and implementation differences such as whether filenames have drive letters in them and where they're stored in a pathname if they do.[160]
You can use the same technique to create a pathname with a different directory component.
(make-pathname :directory '(:relative 'backups') :defaults input-file)
However, this will create a pathname whose whole directory component is the relative directory backups/
, regardless of any directory component input-file
may have had. For example:
(make-pathname :directory '(:relative 'backups')
:defaults #p'/foo/bar/baz.txt') ==> #p'backups/baz.txt'
Sometimes, though, you want to combine two pathnames, at least one of which has a relative directory component, by combining their directory components. For instance, suppose you have a relative pathname such as #p'foo/bar.html'
that you want to combine with an absolute pathname such as #p'/www/html/'
to get #p'/www/html/foo/bar.html'
. In that case, MAKE-PATHNAME
won't do; instead, you want MERGE- PATHNAMES
.
MERGE-PATHNAMES
takes two pathnames and merges them, filling in any NIL
components in the first pathname with the corresponding value from the second pathname, much like MAKE-PATHNAME
fills in any unspecified components with components from the :defaults
argument. However, MERGE- PATHNAMES
treats the directory component specially: if the first pathname's directory is relative, the directory component of the resulting pathname will be the first pathname's directory relative to the second pathname's directory. Thus:
(merge-pathnames #p'foo/bar.html' #p'/www/html/') ==> #p'/www/html/foo/bar.html'
The second pathname can also be relative, in which case the resulting pathname will also be relative.
(merge-pathnames #p'foo/bar.html' #p'html/') ==> #p'html/foo/bar.html'
To reverse this process and obtain a filename relative to a particular root directory, you can use the handy function ENOUGH-NAMESTRING
.
(enough-namestring #p'/www/html/foo/bar.html' #p'/www/') ==> 'html/foo/bar.html'
You can then combine ENOUGH-NAMESTRING
with MERGE-PATHNAMES
to create a pathname representing the same name but in a different root.
(merge-pathnames
(enough-namestring #p'/www/html/foo/bar/baz.html' #p'/www/')
#p'/www-backups/') ==> #p'/www-backups/html/foo/bar/baz.html'
MERGE-PATHNAMES
is also used internally by the standard functions that actually access files in the file system to fill in incomplete pathnames. For instance, suppose you make a pathname with just a name and a type.
(make-pathname :name 'foo' :type 'txt') ==> #p'foo.txt'
If you try to use this pathname as an argument to OPEN
, the missing components, such as the directory, must be filled in before Lisp will be able to translate the pathname to an actual filename. Common Lisp will obtain values for the missing components by merging the given pathname with the value of the variable *DEFAULT-PATHNAME-DEFAULTS*
. The initial value of this variable is determined by the implementation but is usually a pathname with a directory component representing the directory where Lisp was started and appropriate values for the host and device components, if needed. If invoked with just one argument, MERGE-PATHNAMES
will merge the argument with the value of *DEFAULT-PATHNAME-DEFAULTS*
. For instance, if *DEFAULT-PATHNAME-DEFAULTS*
is #p'/home/peter/'
, then you'd get the following:
(merge-pathnames #p'foo.txt') ==> #p'/home/peter/foo.txt'
When dealing with pathnames that name directories, you need to be aware of one wrinkle. Pathnames separate the directory and name components, but Unix and Windows consider directories just another kind of file. Thus, on those systems, every directory has two different pathname representations.
One representation, which I'll call NIL
. If /foo/bar/
is a directory, then both of the following pathnames name it.
(make-pathname :directory '(:absolute 'foo') :name 'bar') ; file form
(make-pathname :directory '(:absolute 'foo' 'bar')) ; directory form
When you create pathnames with MAKE-PATHNAME
, you can control which form you get, but you need to be careful when dealing with namestrings. All current implementations create file form pathnames unless the namestring ends with a path separator. But you can't rely on user-supplied namestrings necessarily being in one form or another. For instance, suppose you've prompted the user for a directory to save a file in and they entered '/home/peter'
. If you pass that value as the :defaults
argument of MAKE-PATHNAME
like this:
(make-pathname :name 'foo' :type 'txt' :defaults user-supplied-name)
you'll end up saving the file in /home/foo.txt
rather than the intended /home/peter/foo.txt
because the 'peter'
in the namestring will be placed in the name component when user-supplied-name
is converted to a pathname. In the pathname portability library I'll discuss in the next chapter, you'll write a function called pathname-as- directory
that converts a pathname to directory form. With that function you can reliably save the file in the directory indicated by the user.
(make-pathname
:name 'foo' :type 'txt' :defaults (pathname-as-directory user-supplied-name))
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