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

时间:2014-10-17 15:48:12

标签: regex emacs

我尝试使用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)

1 个答案:

答案 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进行查找。在伪代码中,它大致如下:

给定regexpreplacementstring

  1. l设置为字符串的长度,将start设置为0。创建一个名为matches的空堆栈,以累积新字符串的片段。

  2. 只要start小于lregexpstring内的某处匹配,请执行以下操作:

    1. 提取与正则表达式匹配的string部分,并将其命名为str

    2. regexp替换为replacement,将替换为较短的字符串str (这很重要)

    3. 将新字符串的以下两个片段推送到matches堆栈:

      • string的不匹配的初始部分,从start到比赛的开头

      • 子字符串str,其中regexp的匹配现已被replacement替换

    4. start设置为匹配部分的结尾并重复。

  3. 最后,以相反的顺序连接matches堆栈上的字符串片段并返回结果。

  4. 原始正则表达式的问题发生在循环的第(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:]]"