如何编写regexp以匹配elisp中最长的候选者?

时间:2015-02-26 00:19:01

标签: regex emacs elisp

我正在尝试编写一个函数来从字符串中删除后缀。后缀如下:

agent_pkg
agent
pkg
driver
abs_if
abs_if_pkg
if_pkg
if

测试字符串:

test_blah_agent_pkg
test_blah_agent
test_blah_pkg
test_blah_driver
test_blah_abs_if
test_blah_abs_if_pkg
test_blah_if_pkg
test_blah_if

从上面的测试字符串中,我希望得到test_blah

我写了一个这样的函数:

(defun get-base-name (name)
  "Get the base name from string."
  (setq s (substring-no-properties name))
  (string-match "\\(.*\\)_\\(agent_pkg\\|agent\\|driver\\|abs_if\\|if\\|pkg\\)" s)  
  (match-string 1 s))

但它总是与短的候选人相匹配。我从test_blah_abs

获得了(get-base-name "test_blah_abs")

1 个答案:

答案 0 :(得分:4)

.*是贪婪的,这意味着只要字符串与正则表达式匹配,它就会尽可能地覆盖。你想让它变得非贪婪,一旦找到匹配就停止。在?*之后添加+会使其变得非贪婪。比较:

(let ((s "abcabcabc"))
  (string-match ".*c" s)
  (match-string 0 s)) ; => "abcabcabc"
(let ((s "abcabcabc"))
  (string-match ".*?c" s)
  (match-string 0 s)) ; => "abc"

.*?.*的非贪婪版本,因此只需添加?即可:

(let ((s "test_blah_agent_pkg
test_blah_agent
test_blah_pkg
test_blah_driver
test_blah_abs_if
test_blah_abs_if_pkg
test_blah_if_pkg
test_blah_if"))
  (string-match "\\(.*?\\)_\\(agent_pkg\\|agent\\|driver\\|abs_if\\|if\\|pkg\\)" s)
  (match-string 1 s)) ; => "test_blah"

FYI,第三方字符串操作库s有许多你认为有用的字符串函数,而不是一直依赖正则表达式。例如。 s-shared-start可以找到2个字符串的公共前缀:

(s-shared-start "test_blah_agent" "test_blah_pkg") ; "test_blah_"

结合s-lines,它将字符串按换行符分解为字符串列表,并使用来自令人惊叹的第三方列表操作库-reducedash函数,你可以找到一个每个字符串都常用的前缀:

(let ((s "test_blah_agent_pkg
test_blah_agent
test_blah_pkg
test_blah_driver
test_blah_abs_if
test_blah_abs_if_pkg
test_blah_if_pkg
test_blah_if"))
  (-reduce 's-shared-start (s-lines s))) ; => "test_blah_"

¹请阅读under section Greediness以了解这一概念。