43.20 文件变更通知

多种操作系统支持监听文件系统中文件或其属性的变更。 如果配置正确,Emacs 会静态链接对应的库,例如 inotifykqueuegfilenotifyw32notify。这些库可用于监听本地机器的文件系统。

同时也可以监听远程机器上的文件系统, see Remote Files in The GNU Emacs Manual。 该功能不依赖于 Emacs 链接的上述任何库。

由于所有这些库在文件变更通知时会产生不同的事件,Emacs 提供了专用库 filenotify, 为应用程序提供统一的接口。需要接收文件通知的 Lisp 程序应始终优先使用该库, 而非原生库。本节记录 filenotify 库的函数与变量。

Function: file-notify-add-watch file flags callback

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 是事件描述,可为以下符号之一:

created

file 已创建

deleted

file 已删除

changed

file 内容已变更;使用 w32notify 库时,该事件也会上报属性变更

renamed

file 已重命名为 file1

attribute-changed

file 属性已变更

stopped

已停止监听 file

注意:w32notify 库不会上报 attribute-changed 事件。 当文件属性(如权限、修改时间)变更时,该库会上报 changed 事件。 同样,kqueue 库在监听目录时,无法可靠上报文件属性变更。

stopped 事件表示文件监听已终止。 原因可能是调用了 file-notify-rm-watch(见下文)、 被监听的文件被删除、被监听文件所在的文件系统被卸载, 或底层库上报了其他错误,导致无法继续监听。

filefile1 是触发事件的文件名称,示例:

(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 动作取决于所使用的监听库。 若不支持,则会以随机顺序返回 deletedcreated 动作。

(rename-file "/tmp/foo" "/tmp/bla")
     ⇒ Event (35025468 renamed "/tmp/foo" "/tmp/bla")

(delete-file "/tmp/bla")
     ⇒ Event (35025468 deleted "/tmp/bla")
Function: file-notify-rm-watch descriptor

移除由 descriptor 指定的已存在文件监听。 descriptor 必须是 file-notify-add-watch 返回的对象。

Command: file-notify-rm-all-watches

移除 Emacs 中所有已存在的文件通知监听。

请谨慎使用该命令,因为它可能对依赖文件监听的包产生意外副作用。 该命令主要用于调试场景,或 Emacs 发生阻塞时。

Function: file-notify-valid-p descriptor

检查由 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