多种操作系统支持监听文件系统中文件或其属性的变更。 如果配置正确,Emacs 会静态链接对应的库,例如 inotify、kqueue、 gfilenotify 或 w32notify。这些库可用于监听本地机器的文件系统。
同时也可以监听远程机器上的文件系统, see Remote Files in The GNU Emacs Manual。 该功能不依赖于 Emacs 链接的上述任何库。
由于所有这些库在文件变更通知时会产生不同的事件,Emacs 提供了专用库 filenotify,
为应用程序提供统一的接口。需要接收文件通知的 Lisp 程序应始终优先使用该库,
而非原生库。本节记录 filenotify 库的函数与变量。
为 file 添加文件系统事件监听。 该操作会将与 file 相关的文件系统事件上报给 Emacs。
返回值为新增监听的描述符。其类型取决于底层库,
通常不能像下面示例那样假定为整数。该描述符仅应通过 equal 进行比较。
如果 file 因某种原因无法被监听,该函数会抛出 file-notify-error 错误。
某些情况下,挂载的文件系统无法监听文件变更。
该函数无法检测这种情况,因此返回非 nil 值不代表 file 的变更一定会被实际通知。
若 file 是符号链接,函数不会追踪该链接,仅监听 file 自身。
flags 是条件列表,用于指定监听内容,可包含以下符号:
change监听文件内容的变更
attribute-change监听文件属性的变更,例如权限或修改时间
若 file 是目录,change 会监听该目录下的文件创建与删除。
部分原生文件通知库在该场景下也会上报文件内容变更。该监听不支持递归。
当事件触发时,Emacs 会调用 callback 函数,并传入单个参数 event,格式为:
(descriptor action file [file1])
descriptor 与本函数返回的对象一致。action 是事件描述,可为以下符号之一:
createdfile 已创建
deletedfile 已删除
changedfile 内容已变更;使用 w32notify 库时,该事件也会上报属性变更
renamedfile 已重命名为 file1
attribute-changedfile 属性已变更
stopped已停止监听 file
注意:w32notify 库不会上报 attribute-changed 事件。
当文件属性(如权限、修改时间)变更时,该库会上报 changed 事件。
同样,kqueue 库在监听目录时,无法可靠上报文件属性变更。
stopped 事件表示文件监听已终止。
原因可能是调用了 file-notify-rm-watch(见下文)、
被监听的文件被删除、被监听文件所在的文件系统被卸载,
或底层库上报了其他错误,导致无法继续监听。
file 和 file1 是触发事件的文件名称,示例:
(require 'filenotify)
⇒ filenotify
(defun my-notify-callback (event)
(message "Event %S" event))
⇒ my-notify-callback
(file-notify-add-watch
"/tmp" '(change attribute-change) 'my-notify-callback)
⇒ 35025468
(write-region "foo" nil "/tmp/foo")
⇒ Event (35025468 created "/tmp/.#foo")
Event (35025468 created "/tmp/foo")
Event (35025468 changed "/tmp/foo")
Event (35025468 deleted "/tmp/.#foo")
(write-region "bla" nil "/tmp/foo")
⇒ Event (35025468 created "/tmp/.#foo")
Event (35025468 changed "/tmp/foo")
Event (35025468 deleted "/tmp/.#foo")
(set-file-modes "/tmp/foo" (default-file-modes) 'nofollow)
⇒ Event (35025468 attribute-changed "/tmp/foo")
是否返回 renamed 动作取决于所使用的监听库。
若不支持,则会以随机顺序返回 deleted 和 created 动作。
(rename-file "/tmp/foo" "/tmp/bla")
⇒ Event (35025468 renamed "/tmp/foo" "/tmp/bla")
(delete-file "/tmp/bla")
⇒ Event (35025468 deleted "/tmp/bla")
移除由 descriptor 指定的已存在文件监听。
descriptor 必须是 file-notify-add-watch 返回的对象。
移除 Emacs 中所有已存在的文件通知监听。
请谨慎使用该命令,因为它可能对依赖文件监听的包产生意外副作用。 该命令主要用于调试场景,或 Emacs 发生阻塞时。
检查由 descriptor 指定的监听是否有效。
descriptor 必须是 file-notify-add-watch 返回的对象。
监听失效的场景包括:被监听的文件或目录被删除、监听线程因其他原因异常退出。
调用 file-notify-rm-watch 移除监听也会使其失效。
(make-directory "/tmp/foo")
⇒ Event (35025468 created "/tmp/foo")
(setq desc
(file-notify-add-watch
"/tmp/foo" '(change) 'my-notify-callback))
⇒ 11359632
(file-notify-valid-p desc)
⇒ t
(write-region "bla" nil "/tmp/foo/bla")
⇒ Event (11359632 created "/tmp/foo/.#bla")
Event (11359632 created "/tmp/foo/bla")
Event (11359632 changed "/tmp/foo/bla")
Event (11359632 deleted "/tmp/foo/.#bla")
;; 删除目录中的文件不会使监听失效
(delete-file "/tmp/foo/bla")
⇒ Event (11359632 deleted "/tmp/foo/bla")
(write-region "bla" nil "/tmp/foo/bla")
⇒ Event (11359632 created "/tmp/foo/.#bla")
Event (11359632 created "/tmp/foo/bla")
Event (11359632 changed "/tmp/foo/bla")
Event (11359632 deleted "/tmp/foo/.#bla")
;; 删除目录会使监听失效
;; 事件会使用不同的监听描述符上报
(delete-directory "/tmp/foo" 'recursive)
⇒ Event (35025468 deleted "/tmp/foo")
Event (11359632 deleted "/tmp/foo/bla")
Event (11359632 deleted "/tmp/foo")
Event (11359632 stopped "/tmp/foo")
(file-notify-valid-p desc)
⇒ nil