《elisp 每日一练》——文件属性

《elisp 每日一练》——文件属性

Masimaro
2026-09-07 / 0 评论 / 0 阅读 / 正在检测是否收录...

很抱歉,因为上个周末有一些事情要处理所以停更了两天。现在我们恢复更新。

在之前我们介绍了文件读写的一些api,以及使用 with-temp-bufferinsert-file-content 实现低成本的文件读取。今天我们来介绍文件属性相关的内容

背景知识铺垫

文件属性获取到的信息与其他编程语言获取文件信息类似,我将它分为以下几种属性的操作:

路径解析

这类api我们需要传入一个无歧义的文件路径,但是路径可以不必真实存在。

  • expand-file-name 将相对路径或者带有 ~ 家目录字符的路径转化为绝对路径
  • file-name-directory 获取参数传入的文件路径所在的目录
  • file-name-nondirectory 去掉文件路径中的目录,得到单纯的文件名
  • file-name-extension 获取文件的扩展名
  • file-name-concat 路径拼接

路径拼接这部分,我们可以使用 expand-file-name 或者 file-name-concat,在Emacs 28以后推荐使用 file-name-concat 。它可以将传入的多个路径参数转化为一个完整的目录, 例如

(file-name-concat "~" ".emacs.d" "init.el") ;; ==> ~/.emacs.d/init.el
(expand-file-name "init.el" "~/.emacs.d") ;; ==> C:/Users/user/AppData/Roaming/.emacs.d/init.el

expand-file-name 最多只能接受两个参数,第一个参数是文件的相对路径,而第二个参数是文件所在目录的路径。从上面的例子中可以看出,file-name-concat 缺少了一个转化 ~ 字符的功能。对于Windows上来讲,有时候使用 ~ 作为家目录可能会出现问题,虽然环境变量中已经给出了家目录但是,Emacs真正在读取对应文件时不一定认,所以最好的办法是使用 expand-file-name 转化为Windows上的绝对路径

文件状态与断言函数

在获取文件之前我们可以快速的对上述文件路径进行快速校验:

  • file-exists-p 文件或者目录是否存在
  • file-regular-p 是否为普通文件,排除目录、符号链接、Socket等特殊文件
  • file-symlink-p 是否为符号链接,如果是,则返回链接指向的真实文件的路径
  • file-readable-p / file-writeable-p 文件是否具有读写权限

文件属性读取

当我们确认了文件真实存在之后就可以调用函数 file-attributes 获取文件的真实属性了。函数签名如下:

(file-attributes FILENAME &optional ID-FORMAT)

FILENAME 这个参数比较好理解,就是一个文件路径的字符串。我们需要重点关注一下 ID-FORMAT 这个参数。ID-FORMAT 参数用于控制返回的文件属主(UID)和属组(GID)信息的格式。

它接受两个特定的符号作为值:

  • integer 默认值,返回数字类型的用户ID和用户组ID
  • string 以字符串的形式尽量返回详细的用户名和用户组名

我们以具体的例子来讲解

(let ((file (expand-file-name "init.el" "~/.emacs.d")))
  (message "%s" (file-attributes file 'string)))

我们得到这样一个返回值

(nil 1 user user (27294 6305 0 0) (27286 33330 0 0) (27258 57596 0 0) 1034 -rw-rw-rw- t 17451448556305724 1353222740)
  • 列表的第0个元素表示类型,t 代表着这是一个目录, nil 表示这是一个文件
  • 第1个元素表示链接数
  • 第2个和第3个表示用户和用户组,因为我们的 ID-FORMAT 指定了 'string 所以它以字符串的形式显示名称
  • 第4、5、6是一个时间戳,表示最后访问时间、最后修改时间和状态最后被修改的时间
  • 第7个元素是文件大小,单位是字节
  • 第8个元素是文件权限
  • 第9个元素是inode 节点
  • 第10个元素是设备标识符

三个时间戳都是以 (SECOND MICROSECOND PICONANOSECOND PICONANOSECOND) 列表格式返回的,对这个时间戳可以使用 format-time-string 来将其转化为人类可以读的字符串

(let* ((file (expand-file-name "init.el" "~/.emacs.d"))
      (attribute (file-attributes file 'string)))
  (message "last modified:%s" (format-time-string "%Y-%m-%d %H:%M:%S" (nth 5 attribute)))) ;; ==> "last modified:2026-09-01 15:43:46"

既然上述信息都是固定的,那么我们就可以封装一些意义更明确的函数,例如 get-file-attribute-mode 之类的函数从返回的属性列表中获取文件权限信息。这个工作Emacs已经为我们做了,针对属性中的内容 Emacs 有一组以 file-attribute- 开头的函数,我们可以使用 C-h f 查看这一组函数

练习

我们使用上述知识点做一个练习,以 ls 的格式输出文件详细属性信息。

(defun my/format-file-attributes (file-path)
  (let ((abs-file-path (expand-file-name file-path)))
    (if (not (file-exists-p abs-file-path))
        (error "File :%s not exists" abs-file-path)
      (let* ((attrs (file-attributes abs-file-path 'string))
             (modes (file-attribute-modes attrs))
             (owner (file-attribute-user-id attrs))
             (group (file-attribute-group-id attrs))
             (size (file-attribute-size attrs))
             (modified-time (format-time-string "%Y-%m-%d %H:%M:%S" (file-attribute-modification-time attrs)))
             (filename (file-name-nondirectory abs-file-path)))
        (format "%-11s %-8s %-8s %8d Bytes %s %s"
                modes
                owner
                group
                size
                modified-time
                filename)))))

(defun my/show-file-attributes(file-name)
  (interactive "fFile Path:")
  (let ((buffer (generate-new-buffer "FileAttribute"))
        (attr-message (my/format-file-attributes file-name)))
    (switch-to-buffer buffer)
    (erase-buffer)
    (goto-char (point-min))
    (insert attr-message)))

总结

本篇我们介绍了有关文件属性的一些函数,今天介绍的主要函数有:

  • expand-file-name 将相对路径或者带有 ~ 家目录字符的路径转化为绝对路径
  • file-name-directory 获取参数传入的文件路径所在的目录
  • file-name-nondirectory 去掉文件路径中的目录,得到单纯的文件名
  • file-name-extension 获取文件的扩展名
  • file-name-concat 路径拼接
  • file-exists-p 文件或者目录是否存在
  • file-regular-p 是否为普通文件,排除目录、符号链接、Socket等特殊文件
  • file-symlink-p 是否为符号链接,如果是,则返回链接指向的真实文件的路径
  • file-readable-p / file-writeable-p 文件是否具有读写权限
  • file-attributes 读取文件或者目录的属性

关于属性读取,我们可以使用 nth 加上索引来获取列表中的内容,也可以调用 file-attribute 开头的一组函数来获取。

0

评论 (0)

取消