.*
is greedy¹, meaning it would try to cover as much as possible, as long as the string matches the regex. You want to make it non-greedy, to stop as soon as the match is found. Adding ?
just after *
or +
makes it non-greedy. Compare:
(let ((s "abcabcabc"))
(string-match ".*c" s)
(match-string 0 s)) ; => "abcabcabc"
(let ((s "abcabcabc"))
(string-match ".*?c" s)
(match-string 0 s)) ; => "abc"
.*?
is a non-greedy version of .*
, so just adding ?
makes it work:
(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, third-party string manipulation library s
has plenty of string functions that you mind useful instead of relying on regular expressions all the time. E.g. s-shared-start
can find a common prefix for 2 strings:
(s-shared-start "test_blah_agent" "test_blah_pkg") ; "test_blah_"
Combined with s-lines
, which breaks a string into a list of strings by newline character, and -reduce
function from the amazing third-party list manipulation library dash
, you can find a prefix that is common for every string:
(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_"
¹ Read under section Greediness to understand this concept.