Reputation: 542
May be it is a really dumb question, but after playing around with all built-in pathname-family functions and cl-fad
/pathname-utils
packages I still can't figure out how to convert a relative path to absolute (with respect to $PWD):
; let PWD be "/very/long/way"
(abspath "../road/home"); -> "/very/long/road/home"
Where the hypothetical function abspath
works just like os.path.abspath()
in Python.
Upvotes: 9
Views: 3028
Reputation: 542
Here's the final solution (based on the previous two answers):
(defun abspath
(path-string)
(uiop:unix-namestring
(uiop:merge-pathnames*
(uiop:parse-unix-namestring path-string))))
uiop:parse-unix-namestring
converts the string argument to a pathname, replacing .
and ..
references; uiop:merge-pathnames*
translates a relative pathname to absolute; uiop:unix-namestring
converts the pathname back to a string.
Also, if you know for sure what kind of file the path points to, you can use either:
(uiop:unix-namestring (uiop:file-exists-p path))
or
(uiop:unix-namestring (uiop:directory-exists-p path))
because both file-exists-p
and directory-exists-p
return absolute pathnames (or nil
, if file does not exist).
UPDATE:
Apparently in some implementations (like ManKai Common Lisp) uiop:merge-pathnames*
does not prepend the directory part if the given pathname lacks ./
prefix (for example if you feed it #P"main.c"
rather than #P"./main.c"
). So the safer solution is:
(defun abspath
(path-string &optional (dir-name (uiop:getcwd)))
(uiop:unix-namestring
(uiop:ensure-absolute-pathname
(uiop:merge-pathnames*
(uiop:parse-unix-namestring path-string))
dir-name)))
Upvotes: 3
Reputation: 38789
Here is what the documentation of UIOP says about cl-fad :-)
UIOP completely replaces it with better design and implementation
A good number of implementations ship with UIOP (used by ASDF3), so it's basically already available when you need it (see "Using UIOP" in the doc.). One of the many functions defined in the library is uiop:parse-unix-namestring
, which understands the syntax of Unix filenames without checking if the path designates an existing file or directory. However the double-dot is parsed as :back
or :up
which is not necessarily supported by your implementation. With SBCL, it is the case and the path is simplified. Note that pathnames allows to use both :back
and :up
components; :back
can be simplified easily by looking at the pathname only (it is a syntactic up directory), whereas :up
is the semantic up directory, meaning that it depends on the actual file system. You have a better chance to obtain a canonical file name if the file name exists.
You can also call TRUENAME
, which will probably get rid of the ".." components in your path. See also 20.1.3 Truenames which explains that you can point to the same file by using different pathnames, but that there is generally one "canonical" name.
Upvotes: 4
Reputation: 780673
The variable *DEFAULT-PATHNAME-DEFAULTS*
usually contains your initial working directory, you can merge the pathname with that;
(defun abspath (pathname)
(merge-pathnames pathname *default-pathname-defaults*))
And since this is the default for the second argument to merge-pathnames
, you can simply write:
(defun abspath (pathname)
(merge-pathnames pathname))
Upvotes: 6