Introduction
In our last lesson, you learned how to pinpoint and extract specific pieces of information from a string using string-match and match-string. This gave you the power to parse text, but parsing is often just the first step. The real magic happens when you can transform that text on the fly.
This lesson focuses on precisely that: performing string substitution using replace-regexp-in-string. We'll take the patterns you know how to write and the groups you know how to capture, and use them to build new strings. This is a cornerstone of text automation, directly supporting your goal of creating custom Elisp tools for your writing and content workflows. Imagine automatically reformatting dates, standardizing terminology, or even converting custom shorthand into final markup—all tasks that this lesson will empower you to tackle.
1. Simple Substitution with Backreferences
The most straightforward kind of replacement involves capturing parts of a string and rearranging them. The function for this is replace-regexp-in-string.
Its basic signature is:(replace-regexp-in-string REGEXP REPLACEMENT STRING)
REGEXP: The pattern to search for.REPLACEMENT: A string defining what to replace the match with.STRING: The original string to operate on.
The function returns a new string with the replacements made; it does not modify the original string.
The key to simple replacement is that the REPLACEMENT string can contain backreferences. These are special codes like \1, \2, etc., that refer to the text captured by your $...$ groups.
\0refers to the entire matched text.\1refers to the text from the first captured group.\2refers to the text from the second captured group, and so on.
Let's see this in your *scratch* buffer. Imagine you have names in "Last, First" format and you want to switch them to "First Last".
(setq original-name "Tolkien, J.R.R.")
;; REGEXP: Capture the last name and the first name in two groups.
;; "\\([A-Za-z]+\\)" is group 1 (last name)
;; ", " is the literal separator
;; "\\(.*\\)" is group 2 (first/middle names)
(setq name-pattern "\\([A-Za-z]+\\), \\(.*\\)")
;; REPLACEMENT: Use backreferences to reverse the groups.
;; "\\2" refers to "J.R.R."
;; " " is a literal space
;; "\\1" refers to "Tolkien"
(setq reformat-pattern "\\2 \\1")
;; Perform the replacement
(replace-regexp-in-string name-pattern reformat-pattern original-name)
When you evaluate the final expression (C-x C-e), the result will be the new string "J.R.R. Tolkien". This simple mechanism is incredibly useful for reformatting structured text.
2. The Need for Dynamic Replacements
Backreferences are great for rearranging text, but what if you need to transform it? For example, what if you wanted to find a word and replace it with its uppercase version? A simple backreference can't do that.
This is where you need to run Elisp code as part of the replacement process. Emacs provides a powerful way to do this.
To see this idea in action, let's watch a short clip. The video demonstrates this concept in the context of an interactive search-and-replace, but the underlying principle of executing code during a replacement is the same.
Emacs: basics of regular expressions (regexp)
The video "Emacs: basics of regular expressions (regexp)" by Protesilaos Stavrou demonstrates a powerful feature for interactive replacements. We're going to look at the specific syntax used to run an Elisp function on a matched group.
Watch from 27:21 to 30:06. Pay close attention to the \,(upcase \2) syntax. This is telling the interactive query-replace command to take the text from the second captured group (\2) and run the upcase function on it.
The \,(...) syntax is a special feature for interactive commands like query-replace-regexp. It's a fantastic tool, but it leads to a very common point of confusion for new Elisp programmers: this syntax does not work inside replace-regexp-in-string.
If you try to use it, the replacement string will be treated literally.
;; This will NOT work as you might expect!
(replace-regexp-in-string "world" "\\,(upcase \\0)" "Hello world")
;; => "Hello \\,(upcase world)" -- The replacement is literal.
So, how do we achieve this powerful, dynamic replacement in our Elisp code? We use a function.
3. Programmatic Substitution with a Function
This is the most powerful and flexible way to perform substitutions in Elisp. Instead of providing a replacement string, you provide a function.
Let's look at the official documentation to see how this works.
Search and Replace (GNU Emacs Lisp Reference Manual)
The GNU Emacs Lisp Reference Manual explains that the replacement argument to replace-regexp-in-string can be a function. This is the key to our goal.
Find the description of replace-regexp-in-string. Read the paragraph that starts, "Instead of a string, rep can be a function." Focus on what the function receives as an argument and what its return value is used for.
As the manual explains, when the REPLACEMENT argument is a function:
replace-regexp-in-stringfinds a match for yourREGEXP.- It calls your function, passing the entire matched text (the equivalent of
match-string 0) as the single argument. - Critically, the match data is set just before your function is called. This means you can use
match-stringinside your function to access all the captured groups from that specific match. - Your function does its work and returns a new string.
replace-regexp-in-stringuses that returned string as the replacement for the current match.- This process repeats for all matches in the original string.
Let's correct our earlier upcase example using this new knowledge. We'll use a lambda (anonymous) function, which is perfect for this kind of short, targeted task.
(replace-regexp-in-string
"world"
(lambda (match)
;; `match` will be "world". We can use it, or just upcase it.
(upcase match))
"Hello world")
Evaluate this, and you'll get the correct result: "Hello WORLD".
To see a real-world example of someone encountering this exact issue and finding the solution, let's examine a discussion on Stack Exchange.
Am I using 'replace-regexp-in-string' the right way?
This Stack Exchange post, "Am I using 'replace-regexp-in-string' the right way?", perfectly illustrates the problem and the correct solution.
First, read the user's question. Notice how their Elisp script attempts to use the \,(...) syntax in the replacement string, and they report that it's being inserted literally. Then, read the accepted answer. It points to the documentation and shows the correct solution using a lambda function.
This case study is valuable because it reinforces the distinction between the interactive world of Emacs commands and the programmatic world of Elisp functions.
4. A Practical Example: Reformatting Dates
Let's put this all together with an example relevant to your work. As a writer and editor, you often need to ensure consistent formatting. Let's say you're editing a document where dates are in the standard YYYY-MM-DD format, but for publication, they need to be in "Month Day, Year" format (e.g., "October 27, 2023").
We can write a function to do this transformation on any string.
- The String: A sample sentence with a date.
- The Regex: A pattern to capture the year, month, and day in three separate groups.
- The Replacement Function: A
lambdathat will:- Use
match-stringto pull out the year, month, and day from the captured groups. - Convert the month number to a month name.
- Use
formatto construct the new date string.
- Use
(let* ((original-text "The final draft is due on 2024-01-15 for review.")
(date-pattern "\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)")
(months '("January" "February" "March" "April" "May" "June"
"July" "August" "September" "October" "November" "December")))
(replace-regexp-in-string
date-pattern
(lambda (matched-date)
;; The `matched-date` argument is the full "YYYY-MM-DD" string.
;; We don't need it here, but we must have it.
;; The real work is done using the match data that was just set.
(let* (;; Extract groups from the match data
(year-str (match-string 1))
(month-str (match-string 2))
(day-str (match-string 3))
;; Convert month string to a number for list indexing
(month-num (string-to-number month-str)))
;; Construct the new string
(format "%s %s, %s"
(nth (1- month-num) months) ;; Get month name from our list
day-str
year-str)))
original-text))
Copy and evaluate this code in your *scratch* buffer. The result will be: "The final draft is due on January 15, 2024 for review.".
This example demonstrates the complete, powerful pattern: combining a precise regex with a transformation function that uses match-string on the implicitly set match data to build a dynamic replacement.
Conclusion
You have now completed the core toolchain for text processing in Elisp. You can find patterns, extract data from them, and now, transform and replace them.
Key Takeaways:
replace-regexp-in-stringis the primary function for performing regex-based substitutions on strings.- For simple rearrangements, use backreferences (
\1,\2, etc.) in a replacement string. - For dynamic transformations, pass a function (typically a
lambda) as the replacement argument. - This function receives the full matched string as its argument, but more importantly, it can use
match-stringto access the captured groups from the match that triggered it. - The interactive
\,(...)syntax does not work inreplace-regexp-in-string.
Preview of the Next Lesson
We have spent a lot of time manipulating linear sequences of text. However, much of the data we work with has more structure. In our next module, we will explore "Key-Value Data: Alists and Plists." You'll learn how to store and retrieve data using named keys, a fundamental concept for managing configurations, metadata, and other structured information in your Emacs Lisp programs.