14

Is there a function that checks that a string ends with a certain substring? Python has endswith:

>>> "victory".endswith("tory")
True
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166

2 Answers2

13

Just install s.el string manipulation library and use its s-suffix? predicate:

(s-suffix? "turn." "...when it ain't your turn.") ; => t

But if you refuse you to use this library, you have to write your own function. There is string-prefix-p, which is an analog to Python's str.startswith, in subr.el, and it is just a wrapper around compare-strings. According to Emacs 24.3 changelog:

** New function `string-prefix-p'.
(This was actually added in Emacs 23.2 but was not advertised at the time.)

string-suffix-p was added only in Emacs 24.4, so for earlier versions i wrote:

(defun string-suffix-p (str1 str2 &optional ignore-case)
  (let ((begin2 (- (length str2) (length str1)))
        (end2 (length str2)))
    (when (< begin2 0) (setq begin2 0))
    (eq t (compare-strings str1 nil nil
                           str2 begin2 end2
                           ignore-case))))

(when (< begin2 0) (setq begin2 0)) is a workaround, because if you pass negative numbers to compare-strings, it barfs with *** Eval error *** Wrong type argument: wholenump, -1.

If you byte compile the function, it works faster than yves Baumes solution, even though string-match is a C function.

ELISP> (setq str1 "miss."
             str2 "Ayo, lesson here, Bey. You come at the king, you best not miss.")
ELISP> (benchmark-run 1000000 (string-suffix-p str1 str2))
(4.697675135000001 31 2.789847821000066)
ELISP> (byte-compile 'string-suffix-p)
ELISP> (benchmark-run 1000000 (string-suffix-p str1 str2))
(0.43636462600000003 0 0.0)
ELISP> (benchmark-run 1000000 (string-match "miss\.$" str2))
(1.3447664240000001 0 0.0)
Community
  • 1
  • 1
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
  • 1
    `compare-strings` is implemented in C as well, likely as a simple byte-wise memory comparison, whereas `string-match` needs to compile and run a RE state machine for the test. Unsurprisingly that's orders of magnitude slower. –  Mar 14 '14 at 13:52
9

You may use a regex with a call to the string-match function.

(if (string-match-p "tory\\'" "victory")
    (message "victory ends with tory.")
   (message "victory does not ends with tory."))
ideasman42
  • 42,413
  • 44
  • 197
  • 320
yves Baumes
  • 8,836
  • 7
  • 45
  • 74
  • You didn't escape the dot, so in this version it matches any char before txt, not just a dot (e.g. it also matches *footxt*) – Tom Mar 14 '14 at 13:37
  • 3
    -1: `$` matches the end of a **line**. You must use `\\'` to match the end of the string. –  Mar 14 '14 at 13:47
  • 2
    Also, you should use `string-match-p` to preserve `match-data`. –  Mar 14 '14 at 13:50
  • @lunaryorn Also ``(string-match "\\.txt$" "foo.txtAAA")`` returns nil which is the expected result. Do you have a counter-example please? – yves Baumes Mar 14 '14 at 16:01
  • 3
    @yvesBaumes `(string-match "\\.txt$" "foo.txt\nAAA")` – Dmitry Mar 14 '14 at 16:10