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


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.

repeat (count s)

Make a string of s repeated count times.

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

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

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

Wrapper around cl-ppcre:split but:

  • our separator is a simple string, where cl-ppcre takes a regexp,
  • we fix an inconsistency:
(cl-ppcre:split "," ",a,b,,c,") ;; => ("" "a" "b" "" "c")

and we return a trailing "":

(split "," ",a,b,,c,") ;; => ("" "a" "b" "" "c" "")

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


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.

common-prefix (list-of-strings) new in v0.5

Find the common prefix between strings.

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


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.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 <>