Modern, consistent and terse Common Lisp string manipulation library.
A modern and consistent Common Lisp string manipulation library
also on Ultralisp.
modernity, simplicity and discoverability:
(str:trim s)instead of
(string-trim '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout) s)),
str:concat strings instead of an unusual
format construct; one discoverable library instead of many;
consistence and composability, where
sis always the last argument, which makes it easier to feed pipes and arrows.
fixing built-in surprises:
(string-downcase nil) =>
"nil"the string, whereas
The only dependency is
Table of Contents
- A modern and consistent Common Lisp string manipulation library
- Global parameters
- Tweak whitespace
- To longer strings
- To shorter strings
- To and from lists
- To and from files
- Dev and test
- See also
Install with Quicklisp:
Add it in your .asd's project dependencies, and call functions with the
str prefix. It is not recommended to
:use :str in a package. It's safer to use the
Check its version:
To get a newer version, you need to update the Quicklisp dist (think of QL as Debian's apt rather than pip/npm/etc):
Don't have a full Common Lisp development environment yet ? Get Portacle, a portable and multiplatform development environment shipping Emacs, Quicklisp, SBCL and Git. See also editor support (Vim, Lem, Atom, Eclipse,…).
Some parameters are common to various functions and often used:
Consequently we can also manage them with global parameters:
(let ((str:*ignore-case* t)) (str:ends-with? "BAR" "foobar"))
is equivalent to
(str:ends-with? "BAR" "foobar" :ignore-case t)
Remove whitespaces at the beginning and end of
(trim " rst ") ;; => "rst"
Uses the built-in
where whitespaces are
'(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout).
Ensure there is only one space character between words. Remove newlines.
(collapse-whitespaces "foo bar baz") ;; "foo bar baz" ;;T
To longer strings
Join strings in list
separator (either a string or a char) in between.
(join " " '("foo" "bar" "baz")) ;; => "foo bar baz" (join #\Space '("foo" "bar" "baz")) ;; => "foo bar baz"
Join strings into one.
(concat "f" "o" "o") ;; => "foo"
Simple call of the built-in concatenate.
We actually also have
(string/char index s)
Insert the given string (or character) at the index
s and return a
index is out of bounds, just return
(str:insert "l" 2 "helo") ; => "hello" (str:insert "o" 99 "hell") : => "hell"
Make a string of
(repeat 3 "foo") ;; => "foofoofoo"
Respectively prepend or append
s to the front of each item.
(len s &key (pad-side :right) (pad-char #\Space)), pad-left, pad-right, pad-center (new in 0.16, 2019/12)
s with characters until it is of the given length. By default,
add spaces on the right:
(str:pad 10 "foo") "foo "
pad-side: one of
pad-char: the padding character (or string of one character). Defaults to a space. See
(str:pad 10 "foo" :pad-side :center :pad-char "+") "+++foo++++"
If the given length is smaller than the length o
Filling with spaces can easily be done with format:
(format nil "~va" len s) ;; => "foo " (format nil "~v@a" 10 "foo") ;; => " foo" (with @)
To shorter strings
(start end s)
Return the substring of
subseq with differences:
- argument order, s at the end
endcan be lower than 0 or bigger than the length of s.
- for convenience
endcan be nil or t to denote the end of the string.
(is "abcd" (substring 0 t "abcd") "t denotes the end of the string") (is "abcd" (substring 0 nil "abcd") "nil too") (is "abcd" (substring 0 100 "abcd") "end can be too large") (is "abc" (substring 0 -1 "abcd") "end can be negative. Counts from the end.") (is "" (substring 0 -100 "abcd") "end can be negative and too low") (is "" (substring 100 1 "abcd") "start can be too big") (is "abcd" (substring -100 4 "abcd") "start can also be too low") (is "" (substring 2 1 "abcd") "start is bigger than end")
Return the first letter of
(s-first "foobar") ;; => "f" (s-first "") ;; => ""
Return the last letter of
Return the rest substring of
(s-rest "foobar") ;; => "oobar" (s-rest "") ;; => ""
Return the nth letter of
(s-nth 3 "foobar") ;; => "b" (s-nth 3 "") ;; => ""
You could also use
(elt "test" 1) ;; => #\e (string (elt "test" 1)) ;; => "e"
(len s &key ellipsis)
s is longer than
len, truncate it and add an ellipsis at the
... by default).
s is cut down to
len minus the length of
the ellipsis (3 by default).
Optionally, give an
:ellipsis keyword argument. Also set it globally
(shorten 8 "hello world") ;; => "hello..." (shorten 3 "hello world") ;; => "..." (shorten 8 "hello world" :ellipsis "-") ;; => "hello w-" (let ((*ellipsis* "-")) (shorten 8 "hello world")) ;; => "hello w-"
To and from lists
Return list of words, which were delimited by whitespace.
Join the list of strings with a whitespace.
(s &key omit-nulls)
Split string by newline character and return list of lines.
A terminal newline character does not result in an extra empty string (new in v0.14, october 2019).
Join the list of strings with a newline character.
(separator s &key omit-nulls limit start end)
Split into subtrings (unlike cl-ppcre, without a regexp). If
omit-nulls is non-nil, zero-length substrings are omitted.
(split "+" "foo++bar") ;; => ("foo" "" "bar") (split #\+ "foo++bar") ;; => ("foo" "" "bar") (split "+" "foo++bar" :omit-nulls t) ;; => ("foo" "bar")
cl-ppcre has an inconsistency such that when the separator appears at the end, it doesn't return a trailing empty string. But we do since v0.14 (october, 2019).
(separator s &key limit)
split, but split from the end. In particular, this will
be different from
split when a
:limit is provided, but in more
obscure cases it can be different when there are multiple different
ways to split the string.
(rsplit "/" "/var/log/mail.log" :limit 2) ;; => ("/var/log" "mail.log")
(cl-ppcre:split " " "a b c ") ("a" "b" "c") (str:split " " "a b c ") ("a" "b" "c" "")
Because it is a common pattern and it can be clearer than an option coming after many parenthesis.
To and from files
Read the file and return its content as a string.
:external-format: if nil, the system default. Can be bound to
But you might just call
There is also
Write the string
s to the file
filename. If the file does not
exist, create it, if it already exists, replace it.
Returns the string written to file.
s is nil or the empty string:
(empty? nil) ;; => T (empty? "") ;; => T (empty? " ") ;; => NIL
str:non-empty-string-p, which adds a
s is empty or only contains whitespaces.
(blankp "") ;; => T (blankp " ") ;; => T (emptyp " ") ;; => NIL
(start s &key ignore-case)
s starts with the substring
start, nil otherwise. Ignore
case by default.
(starts-with? "foo" "foobar") ;; => T (starts-with? "FOO" "foobar") ;; => NIL (starts-with? "FOO" "foobar" :ignore-case t) ;; => T
string-equal depending on the case, with their
(end s &key ignore-case)
s ends with the substring
end. Ignore case by default.
(ends-with? "bar" "foobar") ;; => T
(substring s &key (ignore-case nil))
Return true if
substring, nil otherwise. Ignore the
:ignore-case t (don't ignore by default).
Based on a simple call to the built-in
search (which returns the
position of the substring).
(list s &key (ignore-case *ignore-case*) (test #'string=))
Return T if
s' is a member of list'. Do not ignore case by default.
s-member's arguments' order is the reverse of CL's
*ignore-case* are not nil, ignore case (using
string-equal instead of
s-member returns T or NIL, instead of the tail of LIST whose first element satisfies the test.
prefix?, prefixp and suffix?, suffixp
s if it is a common prefix (or suffix) between items.
uiop:string-prefix-p prefix s, which returns
prefix is a prefix of
uiop:string-enclosed-p prefix s suffix, which returns
prefix and ends with
Functions to change case: camel-case, snake-case,... (new in 0.15, 2019/11)
We use cl-change-case (go thank him and star the repo!).
The available functions are:
:no-case (s &key replacement) :camel-case (s &key merge-numbers) :dot-case :header-case :param-case :pascal-case :path-case :sentence-case :snake-case :swap-case :title-case :constant-case
More documentation and examples are there.
downcase, upcase, capitalize
(s) fixing a built-in suprise. (new in 0.11)
a new string. They call the built-in
string-capitalize respectively, but they fix
something surprising. When the argument is
nil, the built-ins return
"nil" or "NIL" or "Nil", a string. Indeed, they work on anything:
(string-downcase nil) ;; => "nil" the string ! (str:downcase nil) ;; nil (string-downcase :FOO) ;; => "foo"
These functions return
t if the given string contains at least one
letter and all its letters are lowercase or uppercase, respectively.
(is (downcasep " a+,. ") t "downcasep with one letter and punctuation is true.") (is (downcasep " +,. ") nil "downcasep with only punctuation or spaces is false")
alphap returns t if
s contains at least one character and all characters are
alpha (as in
lettersp works for unicode letters too.
(is (alphap "abcdeé") nil "alphap is nil with accents") (is (lettersp "éß") t "lettersp is t with accents and ß")
alphanump returns t if
s contains at least one character and all characters are alphanumeric (as in
lettersnump also works on unicode letters (as in
Return t if the character / string is an ASCII character / is composed of ASCII characters.
An ASCII character has a
char-code inferior to 128.
Returns t if
s contains at least one character and all characters are numerical (as for
has-alpha-p, has-letters-p, has-alphanum-p
Return t if
s has at least one alpha, letter, alphanum character (as with
(old new s)
Replace the first occurence of
s. Arguments are not regexs.
(replace-first "a" "o" "faa") ;; => "foa"
Uses cl-ppcre:regex-replace but quotes the user input to not treat it as a regex.
(old new s)
Replace all occurences of
s. Arguments are not regexs.
(replace-all "a" "o" "faa") ;; => "foo"
Uses cl-ppcre:regex-replace-all but quotes the user input to not treat it as a regex.
If the replacement is only one character, you can use
(substitute #\+ #\Space "foo bar baz") ;; "foo+bar+baz"
Replace all associations given by pairs in a plist and return a new string.
The plist is a list alternating a string to replace (case sensitive) and its replacement.
(replace-using (list "%phone%" "987") "call %phone%") ;; "call 987"
remove-punctuation (s &key replacement)
Remove the punctuation characters from
s, replace them with
replacement (defaults to a space) and strip continuous whitespace.
(str:remove-punctuation "I say: - 'Hello, world?'") ;; => "I say Hello world"
str:no-case to remove punctuation and return the string as lower-case.
(list-of-strings) (renamed in 0.9)
common-prefix in v0.9)
Find the common prefix between strings.
(str:prefix '(\"foobar\" \"foozz\")) => "foo"
Uses the built-in
mismatch, that returns the position at which
the strings fail to match.
Return a string or nil when the input is the void list.
Find the common suffix between strings.
(substring s &key start end)
Counts the non-overlapping occurrences of
You could also count only the ocurrencies between
(count-substring "abc" "abcxabcxabc") ;; => 3
(count-substring "abc" "abcxabcxabc" :start 3 :end 7) ;; => 1
Returns the value of a cons cell in
alist with key
key is a string.
The second return value is the cons cell, if any was matched.
The arguments are in the opposite order of
cl:assoc's, but are consistent
(s-assoc-value '(("hello" . 1)) "hello") ;; 1 ;; ("hello" . 1) (alexandria:assoc-value '(("hello" . 1)) "hello") ;; NIL (alexandria:assoc-value '(("hello" . 1)) "hello" :test #'string=) ;; 1 ;; ("hello" . 1) (assoc "hello" '(("hello" . 1))) ;; NIL (assoc "hello" '(("hello" . 1)) :test #'string=) ;; ("hello" . 1) (cdr *) ;; 1
A case-like macro that works with strings (CL case's test function is
eql, and that isn't enough for strings).
(str:string-case input ("foo" (do something)) (nil (print "input is nil") (otherwise (print "non of the previous forms was caught.")))
You might also like pattern matching. The example below with trivia is very similar:
(trivia:match "hey" ("hey" (print "it matched")) (otherwise :nothing))
Note that there is also http://quickdocs.org/string-case/.
- 0.20, May, 2021: added
- 0.19.1, May, 2021: speed up
join(by a factor of 4).
- 0.19, October, 2020: added s-member
*0.18.1, September, 2020: fix replace-all edge case when the replacement string ends with two backslashes and a single quote.
- 0.18, June, 2020: added
- 0.17, April 2020:
splitalso accept a char as separator
remove-punctuationthat did not respect the case. Use
from-file"odd number of arguments" error.
- 0.16, November 2019: added
pad-[left, right, center].
- 0.15, October 2019: added functions to change case (based on cl-change-case). added remove-punctuation.
- 0.14, October, 2019: fixed the cl-ppcre inconsistency in
lines. A trailing separator now returns a trailing empty string.
(str:split " " "a b c ") ("a" "b" "c") ;; like cl-ppcre:split
(str:split " " "a b c ") ("a" "b" "c" "")
- august, 2019: deprecated
prune, renamed to
- 0.13 june, 2019
- added case predicates (
- added case predicates (
- 0.11 (Quicklisp end of march, 2019, also in Ultralisp)
str:capitalize, that fix the
splitdoesn't fix cl-ppcre's inconsistency anymore (when the separator appears at the end). See issue #18. So
(str:split "xx" "fooxxbarxx")doesn't return a trailing
s-firstand friends return
nilwhen appropriate, not
suffixfunctions and predicates.
- 0.8 added
- 0.7 added
- 0.6 added
split-omit-nulls(QL, january 2018)
- 0.5 added
- 0.4 added
- 0.3 added
Dev and test
Test with prove.
(ql:quickload :str.test) (load "test/test-str.lisp")
- the Common Lisp Cookbook, strings page.
Inspired by the famous Emacs Lisp's s.el.