在 emacs lisp 中将多个路径组件连接成单个完整路径的正确方法是什么?

问题描述 投票:0回答:8

假设我有变量

dir
file
分别包含表示目录和文件名的字符串。在 emacs lisp 中将它们加入到文件的完整路径中的正确方法是什么?

例如,如果

dir
"/usr/bin"
并且
file
"ls"
,那么我想要
"/usr/bin/ls"
。但如果
dir
"/usr/bin/"
,我仍然想要同样的东西,没有重复的斜杠。

elisp filenames path-manipulation
8个回答
83
投票

阅读目录名称手册,你会找到答案:

给定目录名称,您可以组合 它与相对文件名使用

concat

 (concat dirname relfile)

请务必验证文件名是 在这样做之前相对。如果你使用 绝对文件名,结果 可能在语法上无效或 引用了错误的文件。

如果你想使用目录文件 在进行这样的组合时,你的名字 必须先将其转换为目录 使用

file-name-as-directory
命名:

 (concat (file-name-as-directory dirfile) relfile) 

不要尝试 手动连接斜杠,如

 ;;; Wrong!
 (concat dirfile "/" relfile) 

因为这不是便携式的。总是 使用

file-name-as-directory

其他有用的命令有:

file-name-directory
file-name-nondirectory
以及文件名组件部分中的其他命令。


35
投票

您可以使用

expand-file-name
为此:

(expand-file-name "ls" "/usr/bin")
"/usr/bin/ls"
(expand-file-name "ls" "/usr/bin/")
"/usr/bin/ls"

编辑:这只适用于绝对目录名称。我认为 Trey 的答案是更好的解决方案。


16
投票

我想将多个嵌套目录加入到一个路径上。最初我使用了多个

expand-file-name
调用,如下所示:

(expand-file-name "b" (expand-file-name "a" "/tmp"))
"/tmp/a/b"

然而,这相当冗长,并且是倒着读的。

相反,我编写了一个类似于 Python 的函数

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))))

它的工作原理如下:

(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"

15
投票

对于那些在 2021 年之后提出这个问题并且使用 Emacs 版本 >= 28.1 的人。 elisp 内置函数

file-name-concat
可以完成这项工作。现在简单多了。

可以通过以下按键在 emacs 中找到文档:

C-h f file-name-concat <enter>

将 COMPONENTS 追加到 DIRECTORY 并返回结果字符串。

COMPONENTS 中的元素必须是字符串或 nil。 DIRECTORY 或 COMPONENTS 中的非最终元素可能会也可能不会结束 带斜杠——如果它们不以斜杠结尾,则斜杠将是 在连接之前插入。

其他相关函数记录在文件名组中。
可能在 Emacs 版本 28.1 或之前引入。
此函数不会更改全局状态,包括比赛数据。

(file-name-concat "/usr/bin/" "ls")
;; ==> "/usr/bin/ls"

(file-name-concat "/usr" "bin" "ls")
;; ==> "/usr/bin/ls"

13
投票

这个问题是在 2010 年提出的,但在撰写本文时,它是诸如 “在 elisp 中加入文件路径” 等搜索的热门搜索,所以我想我应该更新答案。

自 2010 年以来,Emacs 领域发生了很大变化。这有点重复的答案,因为它在下面的答案中被简短地提到过,但我会稍微充实一下。现在有一个用于文件交互的专用库

f.el

受到 @magnars 优秀的 s.eldash.el 的启发,f.el 是一个用于在 Emacs 中处理文件和目录的现代 API。

不要尝试重新发明轮子。您应该使用此库进行文件路径操作。你想要的功能是

f-join
:

(f-join "path")                   ;; => "path"
(f-join "path" "to")              ;; => "path/to"
(f-join "/" "path" "to" "heaven") ;; => "/path/to/heaven"

您可能需要先安装该软件包。它应该在 MELPA 上可用。


5
投票

这是我使用的:

(defun catdir (root &rest dirs)
  (apply 'concat (mapcar
          (lambda (name) (file-name-as-directory name))
          (push root dirs))))

与@dbr 的差异:

  1. 返回“emacs 目录名称”,即带有尾部斜杠的值
  2. 如果
    root
    是相对的,则不会扩展路径(见注释)
  3. root
    视为根,而
    joindirs
    将使用以 "/" 开头的
    first
    组件作为根。

注释

许多文件处理函数(全部、大多数、???)将规范化冗余斜杠并在相对路径上调用

expand-file-name
(或类似的),因此 #2 和 #3 可能并不重要。


3
投票

如果您使用方便的文件和目录操作库

f.el
,则只需要
f-join
。下面的代码适用于那些由于某种原因拒绝使用这个库的人。

(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))

这与 Python 的行为完全一样

os.path.join

ELISP> (os-path-join "~" "a" "b" "")
"~/a/b/"
ELISP> (os-path-join "~" "a" "/b" "c")
"/b/c"

string-suffix-p
在 Emacs 24.4 之前不存在,所以我在 Check if a string以 Emacs Lisp 中的后缀结尾编写了自己的


0
投票

通过 Emacs 手册的链接来完成之前所说的内容:

正如其他人之前所说,OP问题的答案是使用

expand-file-name
。 这是一个内置函数,用 C 实现,因此不需要使用任何外部库。

这在 Emacs Lisp 手册标题为扩展文件名的函数部分中进行了描述。

并且根据Emacs在线帮助,这个功能是在Emacs 1.6版本中引入的! 所以...它应该可用!

© www.soinside.com 2019 - 2024. All rights reserved.