Emacs,使用 replace-regexp-in-string 匹配两个正则表达式

3
我试图使用replace-regexp-in-string替换一个字符串中的两个部分,但每次只能让一个部分起作用。这里有一个例子:我想从字符串开头删除#和空格,并从末尾删除换行符。当我将这两个调用合并为一个表达式时,我做错了什么?
;; Test string
(setq inputStr "## Header Stuff
")

;; This doesnt trim the newline
(setq header
      (replace-regexp-in-string "^[#\s]*\\|\n$" "" inputStr) )

;; Each match done separately works though
(setq header
      (replace-regexp-in-string "^[#\s]*" "" inputStr) )
(setq header
      (replace-regexp-in-string "\n$" "" header) )

header
"Header Stuff"

更新:问题似乎出在第一个表达式上,例如这个表达式将换行符和"S"替换为"X"(replace-regexp-in-string "S\\|\n$" "X" inputStr)


2
注意观察 (replace-regexp-in-string "^[#\s]*\\|\n$" "X" inputStr) 返回的结果。 - Barmar
@Barmar 我明白了,为什么“X”没有替换换行符呢? - Rorschach
我想不出来,所以我还没有发布答案。 - Barmar
@Barmar 这个代码在换行符之后放置了“X”,但我不太理解它的含义:(replace-regexp-in-string "^[#\s]*\\|\n$\\'" "X" inputStr) - Rorschach
1
$ 匹配行尾,\\' 匹配字符串结尾。 - Barmar
1个回答

2

看起来replace-regexp-in-string在匹配空字符串的正则表达式上有一些意外行为。下面的正则表达式会按照你的预期运作(请注意,*量词被替换为+):

(let ((input-string "## Header Stuff
"))
  (replace-regexp-in-string "\\`[#\s]+\\|\n*\\'" "" input-string))

原因在于replace-regexp-in-string的内部实现,您可以使用M-x find-function查找。简而言之,它大致执行以下操作:
给定一个regexp、一个replacement和一个string
1. 将l设置为字符串的长度,并将start设置为0。创建一个名为matches的空栈来累积新字符串的片段。 2. 只要start小于lregexpstring中的某个位置匹配,就执行以下操作: - 提取与正则表达式匹配的string部分,并称其为str。 - 在较短的字符串str内替换regexpreplacement(这很重要)。 - 将下列两个新字符串片段推入matches堆栈: - 从start到匹配开始的string未匹配的初始部分。 - 子字符串str,其中regexp的匹配项现已被替换为replacement。 - 将start设置为匹配部分的结尾并重复。 3. 最后,按相反的顺序连接matches堆栈上的字符串片段并返回结果。
您原始的正则表达式在循环的第3步出现问题。即使正则表达式在完整字符串"## Header stuff\n"的末尾正确匹配换行符,当它第二次与一个字符的字符串"\n"匹配时,第一分支(匹配空字符串)优先于第二分支,并用空字符串替换空字符串,未能删除尾随换行符。
这可能是replace-regexp-in-string中的一个错误,但它也展示了正则表达式语义有多么棘手,特别是涉及空字符串时。对我来说,解决方法更易于阅读和理解。
(let ((input-string "## Header Stuff
"))
  (setq input-string (replace-regexp-in-string "\\`[#\s]*" "" input-string))
  (setq input-string (replace-regexp-in-string "\n*\\'" "" input-string))
  input-string)

如果您使用的是最新版本的Emacs(预测试版24.4或更高版本),您也可以使用内置的subr-x包中的string-trim-right函数:

(let ((input-string "## Header Stuff
"))
  (string-trim-right (replace-regexp-in-string "\\`[#\s]*" "" input-string)))

顺便说一下,当我调查这个问题时,我很惊讶地发现Emacs字符串中的\s只是写空格字符的另一种方式。如果你想要类似于Perl的\s通配符的正则表达式行为,你可能需要使用"\\s-"(匹配任何带有空格语法的字符)或"[[:space:]]"

根据代码注释,实现似乎是为了效率而设计的 - 特别是为了避免在每次替换时复制整个字符串,而简单的实现会这样做。 - user725091
1
我同意这似乎不是理想的行为。如果您愿意,可以将其报告为错误(M-x report-emacs-bug)。 - user725091

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接