很抱歉,因为上个周末有一些事情要处理所以停更了两天。现在我们恢复更新。
在之前我们介绍了文件读写的一些api,以及使用 with-temp-buffer 和 insert-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.elexpand-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和用户组IDstring以字符串的形式尽量返回详细的用户名和用户组名
我们以具体的例子来讲解
(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)