我尝试使用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)
。
答案 0 :(得分: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
:
将l
设置为字符串的长度,将start
设置为0
。创建一个名为matches
的空堆栈,以累积新字符串的片段。
只要start
小于l
且regexp
与string
内的某处匹配,请执行以下操作:
提取与正则表达式匹配的string
部分,并将其命名为str
。
将regexp
替换为replacement
,将替换为较短的字符串str
(这很重要)
将新字符串的以下两个片段推送到matches
堆栈:
string
的不匹配的初始部分,从start
到比赛的开头
子字符串str
,其中regexp
的匹配现已被replacement
替换
将start
设置为匹配部分的结尾并重复。
最后,以相反的顺序连接matches
堆栈上的字符串片段并返回结果。
原始正则表达式的问题发生在循环的第(3)步。即使正则表达式在完整字符串"## Header stuff\n"
的末尾正确匹配换行符,但是当它与单字符串"\n"
第二次匹配时,替换的第一个分支 - 匹配一个空字符串 - 优先于第二个字符串,它用空字符串替换空字符串,无法删除尾随换行符。
这可以说是replace-regexp-in-string
中的一个错误,但它也显示了regexp语义是多么棘手,特别是涉及空字符串时。对我而言,解决方案解决方案更易于阅读和理解:
(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或更高版本),你也可以使用内置string-trim-right
包中的subr-x
函数:
(let ((input-string "## Header Stuff
"))
(string-trim-right (replace-regexp-in-string "\\`[#\s]*" "" input-string)))
顺便说一句,我很惊讶地发现在调查这个问题时,Emacs字符串中的\s
只是编写空格字符的另一种方式。如果您希望regexp行为类似于Perl的\s
通配符,您可能希望使用"\\s-"
(匹配任何带有空格语法的字符)或"[[:space:]]"
。