在 emacs lisp 中将多个路径组件加入到单个完整路径中的正确方法是啥?

Posted

技术标签:

【中文标题】在 emacs lisp 中将多个路径组件加入到单个完整路径中的正确方法是啥?【英文标题】:What is the correct way to join multiple path components into a single complete path in emacs lisp?在 emacs lisp 中将多个路径组件加入到单个完整路径中的正确方法是什么? 【发布时间】:2011-04-27 06:07:34 【问题描述】:

假设我有变量 dirfile 分别包含代表目录和文件名的字符串。在 emacs lisp 中将它们加入到文件的完整路径中的正确方法是什么?

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

【问题讨论】:

标题的措辞(“join multiple path components”)确实比实际问题更笼统,但如果有人正在寻找处理“multiple”> 2值的解决方案,见***.com/questions/9694661 好吧,如果你可以连接两个组件,那么你可以使用递归来连接任意数字。我想我认为这两个问题通过归纳等价。 【参考方案1】:

通读Directory Names的手册,你会找到答案:

给定一个目录名,你可以组合 它使用相对文件名 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-directoryfile-name-nondirectory,以及File Name Components 部分中的其他命令。

【讨论】:

呃,难怪我到处都能看到那些concat,甚至我们的手册也鼓励这种用法。但通常使用expand-file-name 会更好,除非您希望最终结果是相对文件。 为什么expand-file-nameconcat 更受欢迎?除了风格问题,我没有看到实际的好处? 如果对可移植性感兴趣,您是否还想在结果上致电convert-standard-filename @SamBrightman:不,convert-standard-filename 基本上只能与 constants 一起使用,例如定义用于~/.emacs 的实际名称。大多数其他用途只是混淆了。 好的,但是手册建议它命名例如DOS兼容;这似乎并不特定于常量。为什么我不想确保这种兼容性?例如。我从前缀和一些数据构建完整路径 - 我宁愿获得平台安全版本而不是错误。【参考方案2】:

您可以为此使用expand-file-name

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

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

【讨论】:

【参考方案3】:

我想将多个嵌套目录加入到一个路径中。最初我使用了多个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"

【讨论】:

@dbr,我使用了类似的功能 - 请参阅下面的答案。愿意评论我概述的差异及其优缺点吗?【参考方案4】:

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

自 2010 年以来,Emacs 世界发生了翻天覆地的变化。这是一个重复的答案,因为它在答案below 中被简要提及,但我会稍微充实一下。现在有一个dedicated library 用于文件交互,f.el

受@magnars 优秀的s.el 和dash.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】:

如果你使用方便的文件和目录操作库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 不存在before Emacs 24.4,所以我在Check if a string ends with a suffix in Emacs Lisp 写了我自己的。

【讨论】:

这不是便携式的,是吗?它使用正斜杠作为目录分隔符,并假定根以斜杠开头。【参考方案6】:

这是我使用的:

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

与@dbr 的区别:

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

备注

许多文件处理函数(全部、大多数、???)将规范化多余的斜杠并在相对路径上调用 expand-file-name(或类似的),因此 #2 和 #3 可能并不重要。

【讨论】:

【参考方案7】:

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

正如其他人之前所说,OP 问题的答案是使用expand-file-name。这是一个内置函数,用 C 语言实现,因此不需要使用任何外部库。

这在标题为Functions that Expand Filenames 的Emacs Lisp Manual 部分中有描述。

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

【讨论】:

【参考方案8】:

对于那些在 2021 年之后提出问题的人。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"

【讨论】:

您可能需要注意添加此功能的 emacs 版本。 从文档中,它说“可能在 Emacs 版本 28.1 或之前引入。”

以上是关于在 emacs lisp 中将多个路径组件加入到单个完整路径中的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

请教,关于emacs写lisp自动补全的设置

用于 Emacs Lisp 的 REPL

2020-03-01emacs 中使用LISP

Emacs Lisp 和 Common Lisp 之间的主要区别是啥? [关闭]

Common Lisp 入门配置开发环境 in Emacs

emacs lisp