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)

beware, this is a young and unstable library.

(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) - new in 0.3

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)

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 need uiop's uiop:read-file-string (included in ASDF) and 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.


  • 0.6 added split-omit-nulls
  • 0.5 added common-prefix (upcoming in Quicklisp, february 2018)
  • 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 <>