作为一个文本编辑器,编辑文本是最基本,也是最重要的功能。

本文将介绍关于文件操作的一系列操作,比如查找文件,读写文件,文件信息、读取目录、文件名操作等。

在之前关于vim的介绍时,已经详细的介绍过关于文件、缓冲和窗口的关系。相信各位读者不会弄混这些概念。

在emacs从硬盘上读取文件到缓冲并且显示的过程与vim类似。只是vim会根据后缀设置file type,并根据file type加载相关配置和代码。而emacs会加载各种mode和mode的配置。

文件读写

从硬盘读取一个文件可以使用快捷键 C-x C-f ,它对应的命令是 find-file。它的主要作用是从输入的路径中找到硬盘上存储的文件,并且从文件中读取内容到缓冲区,然后显示缓冲区到窗口。

保存缓冲到文件可以使用 C-x C-s,它对应的命令是 save-buffer。它会将当前缓冲写入到指定的文件中。

在打开文件的过程中,会调用 find-file-noselect,它是打开文件的核心操作,与 find-file 不同,它只返 回访问文件的缓冲区。这两个函数都有一个特点,如果 emacs 里已经有一个缓冲 区访问这个文件的话,emacs 不会创建另一个缓冲区来访问文件,而只是简单返 回或者转到这个缓冲区。

(find-file "~/.zshrc") ;; ==> #<buffer .zshrc>

;; 等效与 find-file
(progn
  (setq foo (find-file-noselect "~/.zshrc"))
  (set-window-buffer nil foo))

如何判断一个缓冲区是否关联了一个文件呢?每个和文件关联的缓冲区都有一个对应的 buffer-local 变量 buffer-file-name。但是不要直 接设置这个变量来改变缓冲区关联的文件。而是使用 set-visited-file-name 来 修改。同样不要直接从 buffer-list 里搜索buffer-file-name 来查找和某个文件关联的缓冲区。应该使用get-file-buffer 或者 find-buffer-visiting

(setq foo (find-file-noselect "~/.zshrc"))
(buffer-file-name foo) ;; ==> "home/xxx/.zshrc"
(get-file-buffer "~/.zshrc") ;; ==> #<buffer .zshrc>
(find-buffer-visiting "~/.zshrc") ;; ==> #<buffer .zshrc

在打开文件过程中会调用 find-file-hook。这里的hook有点像vim中的事件,之前聊到过vim的自动命令,自动命令需要绑定到事件上触发。emacs中有大量的hook,我们通过在hook中添加一些操作来达到这个修改emacs默认行为或者增加新行为的特性。这个我们在后面再说。

另外保存文件会调用一些hook和函数,保存文件之前会调用 before-save-hook,保存之后会调用 after-save-hook

一般来说,在配置emacs的时候,如果需要使用这些函数来读取文件,一般是读取上次退出时保存的临时文件,例如保存工程窗口布局的session文件。这些我们希望读取完成之后不将它们保留到buffer中。这个需求使用 find-file-noselect 是做不到的。必须使用更底层的函数。可以使用 insert-file-contents 和 write-region

(insert-file-contents filename &optional visit beg end replace)
(write-region start end filename &optional append visit lockname mustbenew)

insert-file-contents 可以插入文件中指定部分到当前缓冲区中。如果指定 visit 则会标记缓冲区的修改状态并关联缓冲区到文件,一般是不用的。 replace 是指是否要删除缓冲区里其它内容,这比先删除缓冲区其它内容后插入文 件内容要快一些,但是一般也用不上。insert-file-contents 会处理文件的编 码,如果不需要解码文件的话,可以用 insert-file-contents-literally

write-region 可以把缓冲区中的一部分写入到指定文件中。如果指定 append 则是添加到文件末尾。和 insert-file-contents 相似,visit 参数也会把缓冲 区和文件关联,lockname 则是文件锁定的名字,mustbenew 确保文件存在时会 要求用户确认操作

文件信息

文件是否存在可以使用 file-exists-p 来判断。对于目录和一般文件都可以用 这个函数进行判断,但是符号链接只有当目标文件存在时才返回 t

如何判断文件是否可读或者可写呢?file-readable-pfile-writable-pfile-executable-p 分用来测试用户对文件的权限。文件的位模式还可以用 file-modes 函数得到

(file-readable-p "~/.zshrc") ;; ==>t
(file-writable-p "~/.zshrc") ;; ==>t
(file-executable-p "~/.zshrc") ;; ==>nil
(file-modes "~/.zshrc") ;; ==> 420

文件类型判断可以使用 file-regular-pfile-directory-pfile-symlink-p, 分别判断一个文件名是否是一个普通文件(不是目录,命名管道、终端或者其它 IO 设备)、文件名是否一个存在的目录、文件名是否是一个符号链接。其中 file-symlink-p 当文件名是一个符号链接时会返回目标文件名。文件的真实名字也就是除去相对链接和符号链接后得到的文件名可以用 file-truename 得到。 事实上每个和文件关联的 buffer 里也有一个缓冲区局部变量 buffer-file-truename 来记录这个文件名。

文件更详细的信息可以用 file-attributes 函数得到。这个函数类似系统的 stat 命令,返回文件几乎所有的信息,包括文件类型,用户和组用户,访问日 期、修改日期、status change 日期、文件大小、文件位模式、inode number、 system number

临时文件

如果要产生一个临时文件,可以使用 make-temp-file 这个函数按给定前缀产 生一个不和现有文件冲突的文件,并返回它的文件名。如果给定的名字是一个相 对文件名,则产生的文件名会用 temporary-file-directory 进行扩展。也可以用这个函数产生一个临时文件夹。如果只想产生一个不存在的文件名,可以用 make-temp-name 函数

(make-temp-file "foo")
(make-temp-name "foo")

读取目录内容

可以用 directory-files 来得到某个目录中的全部或者符合某个正则表达式的 文件名。

;; 获取用户目录的文件名称
(directory-files "~/")
;; 获取用户目录的文件全路径
(directory-files "~/")

;; 获取目录中所有cpp文件
(directory-files "~/demo/src" t "\\.cpp$")

另外也可以写一段简单的代码来遍历某个路劲下所有文件

(defun my-list-files (dir)
  "递归遍历目录 DIR 下的所有文件并返回路径列表。"
  (let ((files (directory-files dir t "^[^.]"))
        (result '()))
    (dolist (file files result)
      (if (file-regular-p file)
          (push file result)
        (when (and (file-directory-p file)
                   (not (file-symlink-p file))
                   (file-readable-p file))
          (setq result (append result (my-list-files file))))))))


(my-list-files "~/.emacs.d")

在调用 directory-files 的时候通过一个正则表达式过滤掉所有以.开头的文件,这里主要是为了过滤掉 ... 防止进入无线递归。但是它也误伤了所有隐藏文件。

后面就是比较常规的操作了,遍历返回的list,如果是文件则加入到现有结果集中,如果是目录则进入递归。

directory-files-and-attributesdirectory-files 相似,但是返回的列表 中包含了 file-attributes 得到的信息。file-name-all-versions 用于得到某个文件在目录中的所有版本,file-expand-wildcards 可以用通配符来得到目录中的文件列表。

修改文件信息

重命名和复制文件可以用 rename-filecopy-file。删除文件使用 delete-file。创建目录使用 make-directory 函数。不能用 delete-file 删除 目录,只能用 delete-directory 删除目录。当目录不为空时会产生一个错误。

设置文件修改时间使用 set-file-times。设置文件位模式可以用 set-file-modes 函数。set-file-modes 函数的参数必须是一个整数。你可以用位 函数 logandlogiorlogxor 函数来进行位操作。

(set-file-modes FILENAME MODE &optional FLAG)

其中mode是数字,数字的含义与与chmode 命令类似,例如下面的调用

(set-file-modes "example.txt" #o740)
Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐