首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
276 阅读
2
从零开始配置 vim(15)——状态栏配置
161 阅读
3
nvim番外之将配置的插件管理器更新为lazy
145 阅读
4
2018总结与2019规划
138 阅读
5
PDF标准详解(五)——图形状态
117 阅读
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
emacs
elisp
win32
读书笔记
VimScript
linux
文本编辑器
投资理财
Java
反汇编
OLEDB
数据库编程
Masimaro
累计撰写
406
篇文章
累计收到
32
条评论
首页
栏目
软件与环境配置
读书笔记
编程
Thinking
FIRE
菜谱
翻译
页面
归档
友情链接
关于
搜索到
1
篇与
的结果
2026-08-17
《elisp 每日一练》正则搜索与 rx 宏
看到这个标题我想读者可能有这样的疑问,前面已经介绍了正则表达式了,这次又来,不会是冷饭重炒没什么新东西了吧。实际上Emacs的正则表达式算是比较高级,操作相对复杂的部分了,一篇简单的函数介绍还不足以覆盖。所以在关于文本操作相关的主题中值得专门拿出一篇介绍一些新鲜内容。各位读者请放心往下看,绝不浪费时间。背景知识铺垫在前面我们介绍过,想要使用Emacs中的正则表达式,有下面一个函数:re-search-forward 和 re-search-backward 用于搜索match-string 用于提取搜索到的文本replace-match 用于替换但是Emacs 中使用正则表达式有一个比较坑的点是转义字符必须使用 \\ 来表示一个 \ , 这个细节如果不熟悉Emacs正则表达式的读者可能经常发生明明写了 \\([0-9]+\\) 进行查找结果就是什么都没有,你只能写下 \\\\([0-9]+\\\\) 。 这也太反人类了。好在Emacs解决了这个问题,它提供了 rx 宏。rx宏使我们可以用S 表达式来组织正则表达式。本节我们的重点是介绍rx宏,以便摆脱正则表达式的纠缠。它可以使我们像写elisp代码一样来描述我们的匹配需求。例如看到下面这个Emacs中的正则表达式:"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]\\{2,\\}"我们看到它前面匹配任意字符,中间加一个@符号,后面又是任意字符表示域名,而最后一个 . 后面接至少两个由A到Z组成的后缀。而使用 rx 表达式则可以写成:;; 使用 rx:可读性极大提升 (rx (one-or-more (any "a-z" "A-Z" "0-9" "._%+-")) ;; 第一部分这些任意字符,至少一个 "@" ;; 第二部分是一个@固定字符 (one-or-more (any "a-z" "A-Z" "0-9" ".-")) ;; 第三个部分又是这些字符里面任意字符, 至少一个 "." ;; 第四部分是一个 . (repeat 2 nil (any "a-z" "A-Z"))) ;; 最后一个部分是英文字母但是至少两个我们看到rx 表达式表示的是否更清楚呢。如果你的答案是肯定的,那么本篇的内容值得你继续往下阅读。rx的定义如下(rx REGEXPS...)rx 接收一个或者多个 rx 形式的s表达式,这些形式最终会被翻译成普通的正则表达式字符串,下面按类别介绍最常用的构造。字面量字面量主要有字符串和字符,它们没有特殊含义就是表示字符本身。例如(rx "hello") ; → "hello" (rx ?a) ; → "a" (rx "a.b") ; → "a\\.b"(点被自动转义)序列与选择我们可以通过 (seq FORM...) 或 (sequence FORM...)、(: FORM...)、(and FORM...):按顺序匹配。也可以通过 (or FORM...) 或者 (| FORM...) 表示匹配其中的任意一个。就像上面的例子那样,通过多个基础的rx 单元进行组合,拼接成一个完整的匹配项。又或者如下面的例子:(rx (seq "http" (opt "s") "://")) ; → "https?://" (rx (or "cat" "dog" "bird")) ; → "cat\\|dog\\|bird"重复有下列方式表示重复:zero-or-more 表示0次或者多次one-or-more 表示至少出现一次zero-or-one 表示至多一次,或者说出现0次或1次repeat N 或者 = N 恰好出现N次>= N 至少N次** N M N次到M次rx 中重复默认都采用贪婪模式,如果想要使用非贪婪模式,可以加上 ?字符匹配如果我们想要表示匹配对应集合中任意一个字符,那么可以提供集合,并且通过 (any SET...) / (in SET...) / (char SET...) 表示匹配集合中的任意一个字符。使用 (not CHARSPEC) 匹配不在集合中的字符集合我们可以采用正则表达式中的表现方式例如: a-z 表示小写英文字母,0-9 表示数字。在rx中也可以使用预定义的集合:digit、numeric、num 表示数字alpha、alphabetic、 letter 表示字母alnum 、alphanumeric 表示数字+大写字母+小写字母的序列xdigit 、hex-digit 表示16进制数blank、space / whitespace 表示空白字符word 、 wordchar 表示单词位置匹配如果我们想在上述匹配基础上加上位置,可以考虑使用下面列举的一些内容:line-start、bol:行首(^)line-end、 eol:行尾($)string-start、 bos、 buffer-start:字符串/缓冲区开头(\`)string-end、 eos、 buffer-end:结尾(\')word-start、bow:单词开头(\<)word-end、eow:单词结尾(\>)word-boundary:单词边界(\b)not-word-boundary:非单词边界(\B)symbol-start、symbol-end:符号边界point:当前 point 位置(\=)捕获组对于捕获组,有如下方式:(group FORM...) 、 (submatch FORM...) : 对应着正则表达式中的捕获组 \(...\)(group-n N FORM...) :命名编号的捕获组(backref N):反向引用第 N 个组对于 rx 更加详细的内容,读者可以通过 describe-function 查阅有关 rx 的内容。另外我们也可以通过 rx-to-string 来将一个rx 表达式转化为正则表达式.练习本节的练习,我们利用rx表达式来实现一个将 markdown 中的标题修改为 org mode 中标题的小工具。为了实现这个转换,我们大致需要经历这么几步:通过正则表达式找到 markdown 的标题行获取匹配到的 # 数量以便确定是几级的标题根据#数量替换成等数量的 *根据上面步骤,我写下如下的代码:(defun my/convert-md-tilte-to-org() (interactive) (save-excursion (save-restriction (widen) (goto-char (point-min)) (let ((count 0) (header-regexp (rx line-start (group (repeat 1 6 "#")) (one-or-more space))) ;;位于行首,捕获组1的特征是,# 重复1到6次,后面跟一个或者多个空格 ) (while (re-search-forward header-regexp nil t) (let* ((hashes (match-string 1)) ;; 从捕获组1当中提取匹配到的#字符串 (has-count (length hashes)) ;; 计算#数量 (starts (make-string has-count ?*)) ;; 生成对应数量的 * 字符串 (replacement (concat starts " ")) ;; 组装org mode 标题行 ) (replace-match replacement t t) (setq 1+ count))) (if (> count 0) (message "共找到%d处并替换" count) (message "未找到对应的标题行"))))))小结本节内容主要介绍了通过 rx 来生成生成正则表达式,采用这种方式的好处在于正则表达式代码容易理解,不容易出错,而且还可以带上注释,更进一步降低正则表达式的阅读门槛
2026年08月17日
0 阅读
0 评论
0 点赞