A modern and consistent Common Lisp string manipulation library

(ql:quickload "str")

Why ?

  • modernity, simplicity and discoverability:

  • (str:trim s) instead of (string-trim '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout) s)), or str:concat strings instead of an unusual format construct; one discoverable library instead of many;

  • consistance and composability, where s is always the last argument, which makes it easier to feed pipes and arrows.

The only dependency is cl-ppcre.

Table of Contents


Install with Quicklisp:

(ql:quickload :str)

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):

(ql:update-dist "quicklisp")

beware, this is a young and unstable library. (update v0.7) The functions implementation may change, but we shouldn't change the api.

(don't have a full Common Lisp development environment yet ? Get Portacle, a portable and multiplatform development environment shipping Emacs, Quicklisp, SBCL and Git).

Global parameters

Some parameters are common to various functions and often used: :ignore-case and :omit-nulls.

Consequently we can also manage them with global parameters:

(let ((*ignore-case* t))
  (ends-with? "BAR" "foobar"))

is equivalent to

(ends-with? "BAR" "foobar" :ignore-case t)


Tweak whitespace

trim (s)

Remove whitespaces at the beginning and end of s.

(trim "  rst  ") ;; => "rst"

Also trim-left and trim-right.

Uses the built-in string-trim where whitespaces are '(#\Space #\Newline #\Backspace #\Tab #\Linefeed #\Page #\Return #\Rubout).

To longer strings

join (separator list-of-strings)

Join strings in list list-of-strings with separator in between.

(join " " '("foo" "bar" "baz")) ;; => "foo bar baz"

Uses a specific format syntax.

concat (&rest strings)

Join strings into one.

(concat "f" "o" "o") ;; => "foo"

Simple call of the built-in concatenate.

We actually also have uiop:strcat.

repeat (count s)

Make a string of s repeated count times.

(repeat 3 "foo") ;; => "foofoofoo"

add-prefix, add-suffix (items s) (new in 0.9)

Respectively prepend or append s to the front of each item.

To shorter strings

substring (start end s)

Return the substring of s from start to end.

It uses subseq with differences:

  • argument order, s at the end
  • start and end can be lower than 0 or bigger than the length of s.
  • for convenience end can 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")

s-first (s)

Return the first letter of s.


  (s-first "foobar") ;; => "f"
  (s-first "") ;; => ""

s-last (s)

Return the last letter of s.

s-rest (s)

Return the rest substring of s.


  (s-rest "foobar") ;; => "oobar"
  (s-rest "") ;; => ""

s-nth (n s)

Return the nth letter of s.


  (s-nth 3 "foobar") ;; => "b"
  (s-nth 3 "") ;; => ""

You could also use

(elt "test" 1)
;; => #\e
(string (elt "test" 1))
;; => "e"

Prune (len s &key ellipsis)

If s is longer than len, truncate it and add an ellipsis at the end (... 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 with *ellipsis*.

(prune 8 "hello world")
;; => "hello..."
(prune 3 "hello world")
;; => "..."
(prune 8 "hello world" :ellipsis "-")
;; => "hello w-"
(let ((*ellipsis* "-"))
  (prune 8 "hello world"))
;; => "hello w-"

To and from lists

words (s)

Return list of words, which were delimited by whitespace.

unwords (strings)

Join the list of strings with a whitespace.

lines (s)

Split string by newline character and return list of lines.

unlines (strings)

Join the list of strings with a newline character.

split (separator s &key omit-nulls)

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" :omit-nulls t) ;; => ("foo" "bar")

It is a wrapper around cl-ppcre:split, so it comes with its inconsistency when the separator appears at the end of s:

(cl-ppcre:split "," ",a,b,,c,") ;; => ("" "a" "b" "" "c")

it doesn't return a trailing "".

split-omit-nulls (in v0.6, QL january 2018)

Because it is a common pattern and it can be clearer than an option coming after many parenthesis.

To and from files (experimental in v0.4)

from-file (filename)

Read the file and return its content as a string.

Example: (str:from-file "path/to/file.txt").

:external-format: if nil, the system default. Can be bound to :utf-8.

But you might just call uiop's uiop:read-file-string directly.

There is also uiop:read-file-lines.

to-file (filename s)

Write the string s to the file filename. If the file does not exist, create it, if it already exists, replace it.


  • :if-does-not-exist: :create (default), :error
  • :if-exists: :supersede (default), :append, :overwrite, :rename, :error,...

Returns the string written to file.


empty?, emptyp (s)

True if s is nil or the empty string:

  (empty? nil) ;; => T
  (empty? "")  ;; => T
  (empty? " ") ;; => NIL

blank?, blankp (s)

True if s is empty or only contains whitespaces.

(blankp "") ;; => T
(blankp " ") ;; => T
(emptyp " ") ;; => NIL

starts-with?, starts-with-p (start s &key ignore-case)

True if 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

Calls string= or string-equal depending on the case, with their :start and :end delimiters.

ends-with?, ends-with-p (end s &key ignore-case)

True if s ends with the substring end. Ignore case by default.

(ends-with? "bar" "foobar") ;; => T

contains?, containsp (substring s &key (ignore-case nil))

Return true if s contains substring, nil otherwise. Ignore the case with :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).

prefix?, prefixp and suffix?, suffixp (items s) (new in 0.9)

Return s if it is a common prefix (or suffix) between items.

See also uiop:string-prefix-p prefix s, which returns t if prefix is a prefix of s,

and uiop:string-enclosed-p prefix s suffix, which returns t if s begins with prefix and ends with suffix.


replace-all (old new s)

Replace old by new (no regexs) in s.

(replace-all "a" "o" "faa") ;; => "foo"

Uses cl-ppcre:regex-replace-all but quotes the user input to not treat it as a regex.

prefix (list-of-strings) (renamed in 0.9)

(renamed from common-prefix in v0.9)

Find the common prefix between strings.

Example: (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.

suffix (list-of-strings) (new in 0.9)

Find the common suffix between strings.


string-case (new in v0.8, Quicklisp end of february 2018)

A case-like macro that works with strings (CL's case only works with symbols).


(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 optima is very similar:

(optima:match "hey"
  ("hey" (print "it matched"))
  (otherwise :nothing))

Note that there is also


  • 0.10 (Quicklisp end of august, 2018)
  • split doesn'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 "".
  • added s-last
  • s-first and friends return nil when appropriate, not "".
  • 0.9 (Quicklisp end of may, 2018)
  • added s-first , s-rest and s-nth
  • added prefix and suffix functions and predicates.
  • added prune.
  • 0.8 added string-case
  • 0.7 added version
  • 0.6 added split-omit-nulls (QL, january 2018)
  • 0.5 added common-prefix
  • 0.4 added from-file and to-file.
  • 0.3 added substring.

Dev and test

Test with prove.

See also

  • cl-strings, a similar (discovered afterwards), maybe more complete library, that does not use established libraries as dependencies as we do (with potential implementation issues).
  • cl-change-case to convert strings between camelCase, param-case, snake_case and more.
  • the Common Lisp Cookbook, strings page.

Inspired by the famous Emacs Lisp's s.el.

vindarel <>, vindarel <>
vindarel <>, vindarel <>