11

I am manually constructing path strings in Elisp by concatenating partial paths and directory names. Unfortunately sometimes the paths end with slash, sometimes not. Therefore, I need to insert slash before concatenating a directory name when necessary but not otherwise. What's a good way to do this?

qazwsx
  • 25,536
  • 30
  • 72
  • 106
  • 1
    on linux, doubled // symbols in a file path are usually (always I think) interpreted as a single /, so having an extra one shouldn't be a problem. – Tyler Mar 18 '12 at 03:53
  • This needs to work on all platforms. – qazwsx Apr 10 '12 at 15:22

6 Answers6

14

(file-name-as-directory dir) will return directory path dir with a trailing slash, adding one if necessary, and not otherwise.

If you had your sequence of partial paths in a list, you could do something like:

(let ((directory-list '("/foo" "bar/" "p/q/" "x/y"))
      (file-name "some_file.el"))
  (concat
   (mapconcat 'file-name-as-directory directory-list "")
   file-name))

"/foo/bar/p/q/x/y/some_file.el"

or as an alternative, if you wanted to include the file name in the list, you could utilise directory-file-name which does the opposite of file-name-as-directory:

(let ((path-list '("/foo" "bar/" "p/q/" "x/y/some_file.el")))
  (mapconcat 'directory-file-name path-list "/"))

"/foo/bar/p/q/x/y/some_file.el"

(Someone please correct me if using directory-file-name on a non-directory is not portable?)

phils
  • 71,335
  • 11
  • 153
  • 198
  • What if it has a piece starting with `/` e.g. `"/p/q/"`? – qazwsx Mar 14 '12 at 07:48
  • If it's at the start, then there's no problem (as per my example above). If it's not at the start, well an absolute path has no business being in the middle of a sequence of path components, so that would suggest a bug in the logic that generated that sequence. – phils Mar 14 '12 at 08:05
  • Isn't it a portability problem that you're explicitly using a "/" in the econd example? – Clément Mar 01 '15 at 17:19
  • Clément: No, it's been a very long time since differentiating the directory separator for different systems was recommended. The old `directory-sep-char` variable was removed entirely in Emacs 24, but it's been deprecated since version 21. Assuming that it's Emacs which is going to be using the resulting path, you should use `/` on all systems -- Emacs ensures that it works. – phils Mar 01 '15 at 19:29
4

The easiest way to assemble file names from parts of questionable content is with expand-file-name. For example:

(expand-file-name "foo.txt")

this common form will give you a full file name based on default-directory:

/home/me/foo.txt

but if you have a variable 'dir' whose content is "/home/them/subdir" and want to use that, do this:

(expand-file-name "foo.txt" dir)

it doesn't matter if dir ends in / or not. If you are on some other platform, and contains the other slash, it will do the right thing then too. Do you have a mix? Just stack them:

(expand-file-name "foo.txt" (expand-file-name "somesubdir" dir))
Eric
  • 3,959
  • 17
  • 15
1

Something like this should work as a starting point, although you'd want to flesh it out a bit to make it platform independent, etc.

(defun append-path-component (path new-part)
  (if (string-match ".*/$" path)
    (concat path new-part)
    (concat path "/" new-part)))

As per usual, there's probably some bit of elisp that already does this that I'm just not aware of.

deong
  • 3,820
  • 21
  • 18
  • Thanks. Also, I was looking for those possibly-existing built-in and featureful function. – qazwsx Mar 14 '12 at 02:32
  • This is a portability fail. You shouldn't have to ever type a path separate in your lisp code. – event_jr Mar 14 '12 at 09:47
  • event_jr: `directory-sep-char` is long obsolete, and has been removed entirely in Emacs 24. The old docstring said "The value is always `?/`. Don't use this variable, just use `/`." – phils Mar 17 '12 at 22:12
1

Unless you really care about keeping relative file names as relative, then it's always much better to avoid concat and use expand-file-name instead.

Stefan
  • 101
  • 1
0

If you deal with file manipulation, joining and splitting filepaths, checking empty directories and such, I strongly recommend installing f.el, modern file manipulation library. You will have a huge set of file and filepath manipulation functions under one namespace and will never reinvent the wheel again.

The function you need is f-join, it concatenates parts of a path, adding slash only where needed.

Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
0
(defun* tofilename (directorylist &optional (filename nil))
  "concatenate directory names into a path, with an optional file name as last part"
  (concat
   (mapconcat 'directory-file-name directorylist "/")
   "/"
   filename))


(tofilename '("~/" "Temp/") "temp.txt")
;; => "~/Temp/temp.txt"

(tofilename '("~/" "Temp/"))
;; => "~/Temp/"

(tofilename '("~/" "Temp/" "test"))
;; => "~/Temp/temp/"
Meng Lu
  • 13,726
  • 12
  • 39
  • 47