What is the correct way to join multiple path components into a single complete path in emacs lisp?
Suppose I have variables dir
and file
containing strings representing a directory and a filename, respectively . What is the proper way in emacs lisp to join them into a full path to the file?
For example, if dir
is "/usr/bin"
and file
is "ls"
, then I want "/usr/bin/ls"
. But if instead dir
is "/usr/bin/"
, I still want the same thing, with no repeated slash.
Reading through the manual for Directory Names, you'll find the answer:
Given a directory name, you can combine it with a relative file name using
concat
:(concat dirname relfile)
Be sure to verify that the file name is relative before doing that. If you use an absolute file name, the results could be syntactically invalid or refer to the wrong file.
If you want to use a directory file name in making such a combination, you must first convert it to a directory name using
file-name-as-directory
:(concat (file-name-as-directory dirfile) relfile)
Don't try concatenating a slash by hand, as in
;;; Wrong! (concat dirfile "/" relfile)
because this is not portable. Always use
file-name-as-directory
.
Other commands that are useful are: file-name-directory
, file-name-nondirectory
, and others in the File Name Components section.
You can use expand-file-name
for this:
(expand-file-name "ls" "/usr/bin")
"/usr/bin/ls"
(expand-file-name "ls" "/usr/bin/")
"/usr/bin/ls"
Edit: this only works with absolute directory names. I think Trey's answer is the preferable solution.
I wanted to join multiple nested directories onto a path. Originally I used multiple expand-file-name
calls, like so:
(expand-file-name "b" (expand-file-name "a" "/tmp"))
"/tmp/a/b"
However this is rather verbose, and reads backwards.
Instead I wrote a function which acts like Python's os.path.join
:
(defun joindirs (root &rest dirs)
"Joins a series of directories together, like Python's os.path.join,
(dotemacs-joindirs \"/tmp\" \"a\" \"b\" \"c\") => /tmp/a/b/c"
(if (not dirs)
root
(apply 'joindirs
(expand-file-name (car dirs) root)
(cdr dirs))))
It works like so:
(joindirs "/tmp" "a" "b")
"/tmp/a/b"
(joindirs "~" ".emacs.d" "src")
"/Users/dbr/.emacs.d/src"
(joindirs "~" ".emacs.d" "~tmp")
"/Users/dbr/.emacs.d/~tmp"
This question was asked in 2010, but at the time of writing it's the top hit for searches like "join file paths in elisp", so I thought I'd update the answer.
Since 2010, things have moved on a lot in the world of Emacs. This is somewhat of a duplicate answer since it was mentioned briefly in an answer below, but I'll flesh it out a little. There's now a dedicated library for file interactions, f.el
:
Much inspired by @magnars's excellent s.el and dash.el, f.el is a modern API for working with files and directories in Emacs.
Don't try to reinvent the wheel. You should use this library for file path manipulations. The function you want is f-join
:
(f-join "path") ;; => "path"
(f-join "path" "to") ;; => "path/to"
(f-join "/" "path" "to" "heaven") ;; => "/path/to/heaven"
You may need to install the package first. It should be available on MELPA.
If you use a convenient file and directory manipulation library f.el
, you only need f-join
. The below code is for those, who for some reason refuse to use this library.
(defun os-path-join (a &rest ps)
(let ((path a))
(while ps
(let ((p (pop ps)))
(cond ((string-prefix-p "/" p)
(setq path p))
((or (not path) (string-suffix-p "/" p))
(setq path (concat path p)))
(t (setq path (concat path "/" p))))))
path))
This behaves exactly as Python's os.path.join
.
ELISP> (os-path-join "~" "a" "b" "")
"~/a/b/"
ELISP> (os-path-join "~" "a" "/b" "c")
"/b/c"
string-suffix-p
doesn't exist before Emacs 24.4, so i wrote my own at Check if a string ends with a suffix in Emacs Lisp.