← Back to DevBytes

Emacs Find and Replace: Complete Guide

Introduction to Emacs Find and Replace

Emacs provides one of the most powerful and flexible find-and-replace systems available in any text editor. Unlike many modern editors that separate "find" and "replace" into distinct modal dialogs, Emacs integrates both operations seamlessly into its buffer-centric workflow. Understanding these tools deeply will transform how you navigate and edit text, saving you countless hours of manual editing.

The find-and-replace functionality in Emacs spans several command tiers: interactive search, query-based replacement, regular expression operations, and bulk project-wide replacements. Each tier builds on the previous one, offering progressively more sophisticated control over text transformation.

What It Is: The Core Commands

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

At its foundation, Emacs find-and-replace consists of two complementary families of commands: search commands that move the cursor through a buffer, and replace commands that modify text while searching. The search commands help you navigate; the replace commands help you edit. Together, they form a complete text-manipulation toolkit.

Incremental Search: C-s and C-r

The most fundamental search commands are C-s (isearch-forward) and C-r (isearch-backward). These perform incremental searching, meaning the search begins immediately as you type each character, jumping to the first match in real time. This is distinct from "batch" searching where you type the entire search string first, then initiate the search.

While in an incremental search (the minibuffer shows I-search:), you can:

;; Example workflow for incremental search:
;; 1. Press C-s
;; 2. Type "function" — cursor jumps to first occurrence as you type
;; 3. Press C-s repeatedly to cycle through matches
;; 4. Press RET to stay at the current match
;; 5. Or press C-g to abort and return to where you started

Non-Incremental Search

For situations where you want to enter the full search string before searching begins, use M-x search-forward or M-x search-backward. These are less commonly used interactively but can be useful in keyboard macros and Lisp code.

Why It Matters: The Power of Integrated Search and Replace

The Emacs approach to find-and-replace matters for several critical reasons that directly impact developer productivity:

How to Use It: From Basic to Advanced

Basic String Replacement: M-%

The primary replace command is M-% (query-replace), which performs an interactive, query-based string replacement. For each match, Emacs asks you what to do. The available responses form a powerful decision language:

;; Key responses during query-replace (M-%):
;; y        — Replace this match and move to next
;; n        — Skip this match, don't replace, move to next
;; !        — Replace ALL remaining matches without further prompts
;; .        — Replace this match and then stop (exit)
;; q / RET  — Quit immediately (RET exits; q quits)
;; C-l      — Redisplay the screen, centering the current match
;; C-r      — Enter a recursive edit (C-M-c to exit recursive edit)
;; C-s      — Start incremental search forward from here
;; C-r      — Start incremental search backward from here
;; e        — Edit the replacement string on-the-fly
;; ?        — Display help showing all these options

To perform a basic string replacement:

;; 1. Type M-% (query-replace)
;; 2. When prompted "Query replace:", type: old_function_name
;; 3. When prompted "with:", type: new_function_name
;; 4. For each match, press y to replace, n to skip, or ! to replace all

Regular Expression Replacement: C-M-%

The command C-M-% (query-replace-regexp) extends replacement with full regular expression support. This is where Emacs truly shines. You can capture groups and reference them in the replacement string using \1, \2, etc. The replacement string also supports special case conversion directives.

Common regex constructs in Emacs (which uses a slightly different syntax than PCRE):

;; Example: Swap the order of function arguments
;; Original text:  transform(x, y, z)
;; Desired text:   transform(z, y, x)

;; Step 1: C-M-% (query-replace-regexp)
;; Step 2: Search regex: transform(\([^,]*\), \([^,]*\), \([^)]*\))
;; Step 3: Replace with: transform(\3, \2, \1)

;; The capture groups \1, \2, \3 correspond to x, y, z respectively

Case Conversion in Replacements

Emacs provides special escape sequences in the replacement string for automatic case conversion, which is invaluable when renaming identifiers:

;; Case conversion directives in replacement strings:
;; \u  — Uppercase the next character
;; \l  — Lowercase the next character
;; \U  — Uppercase everything until \e or end
;; \L  — Lowercase everything until \e or end
;; \e  — End the effect of \U or \L
;; \E  — Same as \e

;; Example: Convert snake_case to camelCase
;; Search:  \([a-z]+\)_\([a-z]+\)
;; Replace: \1\U\2\e
;; "my_variable" becomes "myVariable"
;; "user_name" becomes "userName"

Recursive Editing During Replacement

When you press C-r during a query-replace session, Emacs enters a recursive edit. This suspends the replace operation and lets you freely edit the buffer around the current match. This is exceptionally powerful for cases where a simple replacement isn't enough and you need to make contextual adjustments. Press C-M-c to exit the recursive edit and resume the replacement.

;; Workflow for recursive edit during replacement:
;; 1. M-% to start query-replace
;; 2. When you reach a match needing manual adjustment, press C-r
;; 3. The mode line shows [query-replace-edit] or similar
;; 4. Make your edits freely — insert, delete, reformat
;; 5. Press C-M-c to return to the replacement loop
;; 6. Continue with y/n/! as normal

Replacement from the Keyboard Macro Perspective

Both query-replace and query-replace-regexp can be embedded in keyboard macros. When you record a macro with F3 or C-x ( and then perform a query-replace, the entire interaction is captured. When you replay the macro with F4 or C-x e, the replacement replays exactly as recorded — including your y/n choices.

;; Recording a macro that includes replace:
;; C-x (          — Start recording
;; M-%            — Begin query-replace
;; old_text RET   — Search string
;; new_text RET   — Replacement string
;; !              — Replace all (this gets recorded)
;; C-x )          — Stop recording

;; Now C-x e or F4 replays the entire replace operation

Finding and Replacing Across Multiple Files

For project-wide find-and-replace, Emacs offers several approaches. The most interactive is using dired with the dired-do-find-regexp-and-replace command:

;; Method 1: Using dired (built-in)
;; 1. Open dired on your project directory: C-x d
;; 2. Mark the files you want to operate on (or use % m with a regex)
;; 3. Press Q (dired-do-find-regexp-and-replace)
;; 4. Enter the regex to search for
;; 5. Enter the replacement string
;; 6. Respond y/n/! for each match across all marked files

;; Method 2: Using project.el (Emacs 25+)
;; M-x project-query-replace-regexp
;; This searches all files in the current project

Using grep and occur for Finding

Before replacing, you often want to survey all occurrences. The occur command and grep integration serve this purpose:

;; M-x occur — Shows all matches in the current buffer in a separate window
;; Each line in the *Occur* buffer is clickable and jumps to that match
;; From the *Occur* buffer, you can press e to enter edit mode
;; and perform replacements directly in the occur buffer

;; M-x grep — Runs grep and collects results in a grep buffer
;; From the grep buffer, you can navigate to matches
;; and then perform query-replace on the actual files

Searching and Replacing in Lisp Code

For programmatic find-and-replace in your Emacs Lisp extensions, the core functions are:

;; Essential Lisp functions for search and replace:

;; Searching:
(save-excursion
  (goto-char (point-min))
  (re-search-forward "pattern" nil t))  ;; Returns position or nil

;; Replacing in a region:
(save-excursion
  (goto-char (point-min))
  (while (re-search-forward "old" nil t)
    (replace-match "new" nil nil)))

;; Query-replace from Lisp:
(perform-replace "old" "new" t t nil)
;; t t nil = query flag, regexp flag, delimited flag

;; Building a custom replace function:
(defun my-replace-in-buffer (old new)
  "Replace all occurrences of OLD with NEW in current buffer."
  (save-excursion
    (goto-char (point-min))
    (let ((count 0))
      (while (search-forward old nil t)
        (replace-match new nil t)
        (setq count (1+ count)))
      (message "Replaced %d occurrences" count))))

Best Practices

1. Always Preview with Search First

Before executing a bulk replacement, use C-s or M-x occur to survey matches. This gives you a clear picture of what will be affected and helps you refine your regex. For regex replacements, use C-M-s (isearch-forward-regexp) to interactively test your pattern before committing to replacement.

;; Preview regex matches before replacing:
;; C-M-s (isearch-forward-regexp)
;; Type your regex pattern
;; Cycle through matches with C-s
;; If the pattern is correct, press M-% and the search string
;; is already populated — just press RET to confirm

2. Use Narrowing to Restrict Scope

When you want to replace only within a specific portion of a large buffer, use narrowing. The command C-x n n (narrow-to-region) restricts all operations to the selected region. After performing your replacements, C-x n w (widen) restores full buffer visibility.

;; Safe replacement workflow with narrowing:
;; 1. Select the region you want to affect
;; 2. C-x n n (narrow-to-region)
;; 3. M-% or C-M-% to perform replacements
;; 4. The replacement only operates within the narrowed region
;; 5. C-x n w (widen) when done

3. Master the y/n/! Decision Language

The query-replace prompt is a mini language worth memorizing. The most efficient workflow is: scan a few matches with y to verify correctness, then press ! to auto-replace the rest. The . key (replace this one and stop) is perfect for one-off replacements where you don't want to continue.

4. Use Word/Symbol Boundaries in Regex

A common pitfall is replacing a substring that appears inside other identifiers. Always use word boundaries (\< and \>) or symbol boundaries (\_< and \_>) to match only whole identifiers:

;; Problem: Replacing "var" also matches "variable", "varchar", "variegated"
;; Solution with word boundaries:
;; Search:  \<var\>
;; This matches only the standalone word "var"

;; For programming language identifiers (symbols):
;; Search:  \_<var_>
;; This matches "var" as a symbol, respecting underscores, hyphens, etc.

5. Leverage Tags and grep for Large Projects

For projects spanning dozens or hundreds of files, build a TAGS table first with M-x visit-tags-table and then use M-x tags-query-replace. This performs query-replace across all files in the tags table, which is faster and more comprehensive than manual dired selection.

;; Project-wide replace using etags:
;; 1. Generate TAGS:  cd project/ && etags **/*.el **/*.c **/*.h
;; 2. In Emacs: M-x visit-tags-table (select the TAGS file)
;; 3. M-x tags-query-replace
;; 4. Enter regex and replacement
;; 5. Walk through matches across all source files

6. Save Complex Replacements as Macros or Functions

If you find yourself performing the same multi-step find-and-replace operation repeatedly, record it as a keyboard macro (C-x ( ... C-x )) or write a custom Lisp function. This transforms a repetitive manual task into a single reusable command.

;; Example: A reusable function for a common pattern
(defun my-fix-javadoc-returns ()
  "Replace '@return' with '@returns' in Java doc comments."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "@return[^s]" nil t)
      (replace-match "@returns " nil nil))))
;; Bind to a key: (global-set-key (kbd "C-c j r") 'my-fix-javadoc-returns)

7. Use the Built-in Undo to Revert Bad Replacements

Emacs's undo system (C-/ or C-x u) treats the entire query-replace operation as a single undoable step. If you accidentally press ! and replace everything incorrectly, a single undo reverts the entire operation. This safety net encourages experimentation.

8. Edit the Replacement String On-the-Fly

During a query-replace session, pressing e lets you edit the replacement string without exiting the replace loop. This is useful when you notice mid-replacement that the replacement pattern needs adjustment. After editing, press RET and the replacement continues with the new string.

Advanced Techniques

Delimited Replacement

The command M-x query-replace-delimited (or C-u M-%) restricts replacement to matches that are surrounded by word boundaries, effectively treating the search string as a whole word. This is equivalent to wrapping the search string with \< and \> but more convenient for quick operations.

Replacing Across Multiple Buffers

For the same replacement across several open buffers, you can use a combination of ibuffer or manual buffer switching with a recorded macro. Alternatively, use M-x multi-occur to first find all occurrences across buffers, then manually visit and replace in each.

;; Multi-buffer search workflow:
;; M-x multi-occur
;; Specify buffers: *.el, *.c (or select from list)
;; Specify regex
;; The *Occur* buffer shows matches from all specified buffers
;; Click each match to visit the buffer, then perform M-%

Using query-replace-regexp-eval for Programmatic Replacements

For replacements where the replacement string depends on the matched text in complex ways, use M-x query-replace-regexp-eval. This allows you to provide a Lisp expression that computes the replacement dynamically for each match.

;; Example: Increment all numbers by 1
;; M-x query-replace-regexp-eval
;; Search:  [0-9]+
;; Replace expression: (number-to-string (1+ (string-to-number (match-string 0))))
;; This evaluates the Lisp expression for each match

Conclusion

The Emacs find-and-replace system is a deep, composable toolkit that rewards mastery. From the instantaneous feedback of incremental search to the surgical precision of query-replace-regexp, these commands form the backbone of efficient text manipulation in Emacs. The key insight is that find-and-replace in Emacs is not a separate "dialog box" operation — it's an integrated part of the editing flow, accessible at any moment and composable with macros, narrowing, and recursive edits. By internalizing the y/n/! decision language, learning Emacs's regex syntax with its capture groups and case directives, and scaling your operations from single buffers to entire projects with tags and dired, you transform what might be mundane search-and-replace into a fluid, expressive editing language. Take time to practice these commands deliberately; they will repay the investment many times over in your daily editing.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles