Skip to main content
L
Loopaloo
Buy Us a Coffee
All ToolsImage ProcessingAudio ProcessingVideo ProcessingDocument & TextPDF ToolsCSV & Data AnalysisConverters & EncodersWeb ToolsMath & ScienceGames
Guides & BlogAboutContact
Buy Us a Coffee
L
Loopaloo

Free online tools for developers, designers, and content creators. Your files are processed in your browser and are never uploaded - no accounts required. A few network utilities (like What's My IP and Currency Converter) call public APIs to do their job and say so on their pages.

support@loopaloo.com

Tool Categories

  • Image Tools
  • Audio Tools
  • Video Tools
  • Document & Text
  • PDF Tools
  • CSV & Data
  • Converters
  • Web Tools
  • Math & Science
  • Games

Company

  • About Us
  • Contact
  • Blog
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

Support

Buy Us a Coffee

© 2026 Loopaloo. All rights reserved. Built with privacy in mind.

Privacy|Terms|Disclaimer
  1. Home
  2. CSV & Data Analysis
  3. CSV Search & Replace
Add to favorites

CSV Search & Replace

Find and replace with both halves stated: what the pattern matches, and whether $1 and $& in the replacement are patterns or text

Rows never leave your deviceMore csv & data analysisJump to full guide

Initializing in your browser…

You might also like

CSV Viewer & Editor

View, sort and edit a CSV, with an undo stack that goes back exactly one step and a parse report that tells a broken quote from a ragged row

CSV Row Filter

Filter rows with each condition stating its rule: a cell that is not a number does not match a comparison, and an empty cell is not a zero

CSV Column Renamer

Rename columns under a stated convention, with two that would end up with the same name numbered rather than left to collide

A sample run

A price column holds "$1,299.99" and a note column holds the text "TBD". You want the prices as plain numbers, and the notes to read "$&!" exactly as typed.

Two passes over four rows

id,price,note
1,"$1,299.99",TBD
2,$50.00,TBD
3,$7.05,paid

1. In "price", regex [$,] replaced with nothing
2. In "note", the text TBD replaced with $&!
What CSV Search & Replace produces

Result

id,price,note
1,1299.99,$&!
2,50.00,$&!
3,7.05,paid

pass 1: 4 matches in 3 of 3 cells searched, 3 cells changed
pass 2: 2 matches, read exactly as typed
switch to "With $1 and $& patterns" and pass 2 writes TBD! instead

Two things here were impossible before. Replacing with nothing is a deletion, and the previous version treated an empty replacement box as "no replacement asked for" and quietly did nothing while still offering to replace the matches it had found. And the replacement is inserted exactly as typed unless you ask otherwise, so $&! stays $&! rather than becoming the matched text; the reading is a control on the page, because $1 and $& are the point of a regular expression and a nuisance everywhere else. The counts separate matches from cells, so 4 matches in 3 cells is legible: one row had both a dollar sign and a comma.

Find and Replace Where Both Halves Say What They Mean

Search a CSV for text or a regular expression and replace what you find, with two questions answered out loud rather than guessed at: what the pattern matches, and what the replacement means. Scored against Python's own regex engine and the ECMA-262 substitution table on a 45 case corpus, the previous version got 29 of them right and hung the page outright on five.

How to use

  1. 1Upload your CSV, or load the sample
  2. 2Type what to search for, and choose plain text or a regular expression
  3. 3Type the replacement, and choose whether $1 and $& in it are patterns or text
  4. 4Scope it: all columns or one, with or without the header row
  5. 5Read the match count and the rules in force, then Replace All and download

Key features

  • A 45 case corpus scored against Python's regex engine: 29 of 45 before, 45 of 45 after
  • Patterns that can match the empty string terminate, per ECMA-262 22.2.9.2, instead of hanging the page
  • The replacement read exactly as typed by default, or as an ECMA-262 substitution template when you ask
  • An empty replacement deletes the matches, which the previous version silently refused to do
  • A pattern the engine cannot compile reported with its own message, not shown as zero matches
  • Whole word defined as "not adjacent to a word character", so it works in regex mode too
  • Unicode mode wherever the pattern allows it, so a dot is one code point and an emoji is not split
  • Replace and Replace All go through the same expansion, so they cannot disagree
  • Match counts separated from cell counts, and from how many cells would actually change
  • The rules in force written out in sentences with the result
  • Undo, plus a 20 deep history you can restore any point from
  • Single-column CSV files load, instead of being refused as undelimited
  • Downloads written by RFC 4180: CRLF records, minimum quoting, and a UTF-8 byte order mark

How it works

Measured on 2026-09-02 against a corpus whose right answer per case comes from Python's `re` for what a pattern matches and from ECMA-262 22.1.3.19 GetSubstitution, written out, for what a replacement means. Baseline 29 of 45 correct, with 5 cases in which the page never came back. After: 45 of 45, in the module and through the browser.

The worst fault was not a wrong answer, it was no answer. Counting matches used a hand-written `while ((m = re.exec(cell)) !== null)` loop with a global pattern. Under ECMA-262 22.2.7.2 a global match that consumes nothing does not move lastIndex, so the caller has to advance it, and this loop did not. Typing `a*`, `x?`, `^` or `\b` into the search box therefore looped forever inside a memo that runs during render, and the tab stopped responding. Measured: after typing `a*` the page did not answer `1 + 1`. The scan now advances by one code point after a zero-length match, per 22.2.9.2, and `a*` over the whole corpus finds 1,087 matches in about 120 milliseconds.

The replacement was always a substitution template. Replacing something with the literal text `$&` inserted the matched text instead, `$5.00` was at the mercy of whether a fifth capture group happened to exist, and none of this was true of the search box, which escaped metacharacters so a dot meant a dot. The two readings are now a control. "Exactly as typed" inserts the characters you typed, which is the default and is what a plain-text replace should always have done. "With $1 and $& patterns" applies the ECMA-262 table in full: $$, $&, $backtick, $apostrophe, $n, $nn and $<name>, with a group that did not participate substituting as nothing. Turning on Use regex turns the pattern reading on with it, because capture groups are the reason to be there.

Deleting text was impossible. The preview guarded on `if (!searchTerm || !replaceTerm) return csvData`, and an empty string is falsy, so leaving the replacement box empty returned the file unchanged while the tool still offered to replace the matches it had found and reported them as replaced. An empty replacement is a deletion now and is carried out, and the summary says so.

A pattern that cannot be compiled was caught and turned into zero matches with no message, which looks exactly like a pattern that matched nothing. A mistyped `[abc` now reports the engine's own message and says that nothing was searched, which is not the same as nothing matching.

Whole word did nothing at all in regex mode: the word boundaries were added only on the plain-text branch. It applies in both now, and it is defined as "not adjacent to a letter, digit, mark or underscore" rather than as the \b assertion, because \b is a boundary BETWEEN a word and a non-word character and therefore depends on context: `\bC\+\+\b` matches inside `C++11`. Measured on the corpus, the old rule matched C++ inside C++11 and refused café before a comma; the new one does neither.

Patterns compile in Unicode mode wherever they can, which is what makes a dot one code point rather than one UTF-16 code unit, makes the Kelvin sign U+212A fold with a lower case k, makes \p{L} available, and makes a zero-length scan step over an emoji rather than through it (`x?` over `a😀b` used to produce two lone surrogates). The few patterns that are legal without the u flag and errors with it fall back, and the page says which mode is in force.

A one-column CSV was refused outright, because papaparse reports UndetectableDelimiter for a file with no delimiter in it to detect and every Delimiter-type error was treated as fatal. Its own documentation classes that as a warning; the parse has already defaulted to a comma and is correct. And the download goes through the RFC 4180 writer now: CRLF records per section 2.1, quoting as a minimum rather than a preference, and a byte order mark so a spreadsheet reads the file as UTF-8. Python's csv module reads a cell holding a comma, a cell holding a quote and a cell holding a newline back out of the download unchanged.

Where this fits a data pipeline

  • Cleaning a price column

    Delete the currency symbols and grouped-thousands commas with one regex and an empty replacement, so the column can be read as numbers.

  • Migrating a domain or a hostname

    Scope the replace to one column and watch the count: 5 matches in 5 of 20 cells searched tells you the rule hit what you meant before you commit.

  • Reformatting with capture groups

    Reorder a name, restructure a date, or rebuild an identifier with $1 and $2, with the substitution reading turned on explicitly so nothing else in the file is at the mercy of a dollar sign.

  • Renaming a column

    Turn on "Search the header row" and the same rule applies to the header, which the previous version never touched and never said it was skipping.

Tips & best practices

  • The replacement is inserted exactly as typed by default, so a price, a Windows path or anything else holding a dollar sign survives. Switch the reading to "With $1 and $& patterns" when you actually want capture groups.
  • Leave the replacement box empty to delete every match. That is how you strip currency symbols and grouped-thousands commas out of a price column.
  • A pattern the engine refuses is reported with its own message rather than shown as zero matches, so a typo in a character class looks different from a pattern nothing matches.
  • Whole word means "not adjacent to a letter, digit or underscore", so it refuses C++ inside C++11 and accepts café before a comma. The \b assertion does the opposite of both.
  • The header row is not searched unless you ask. Turn on "Search the header row" to rename a column with the same rule you are applying to the data.
  • Use the History panel rather than just Undo: it keeps the last 20 states with descriptions and timestamps, so you can jump back several steps after a wrong Replace All.

Examples

  • Strip currency decoration

    Turn on Use regex, search [$,] and leave the replacement empty, scoped to the price column. Four matches across three cells become 1299.99, 50.00 and 7.05.

  • Replace with text holding a dollar sign

    Search TBD and replace with $5.00 while the reading is "Exactly as typed". The replacement lands as $5.00. Under the pattern reading it also lands as $5.00, because $5 is a group that does not exist, but $&! would become the matched text.

  • Reorder a name with capture groups

    Turn on Use regex, which switches the reading to patterns, search ^(\w+), (\w+)$ and replace with $2 $1 to turn "Smith, John" into "John Smith".

Frequently asked questions

Why did typing a pattern like a* used to freeze the page?

Because a global regular expression that matches the empty string does not advance its own scan position: ECMA-262 leaves that to the caller, and the loop counting matches never did it. The scan now steps forward by one code point after a zero-length match, so a*, x?, ^ and \b all terminate. That was measured, not assumed: after typing a* the old page could not evaluate 1 + 1.

Why is my replacement text coming out differently from what I typed?

It should not be, unless you asked for it. The default reading inserts the replacement character for character. If you switch to "With $1 and $& patterns", the ECMA-262 substitution table applies and $& becomes the matched text, $1 a capture group and $$ a single dollar sign. Turning on Use regex turns that reading on with it.

Can I delete text rather than replace it?

Yes. Leave the replacement box empty and every match is removed. The summary and the rules list both say so before you commit.

Does it support regex capture groups?

Yes, both numbered and named. Use $1 and $2, or $<year> and $<month> for named groups, with the reading set to "With $1 and $& patterns". A group that did not participate in the match substitutes as nothing, which is what ECMA-262 specifies.

What does "Whole word" actually mean here?

That the match is not adjacent to a letter, digit, mark or underscore on either side. That is deliberately not the \b assertion, which is a boundary between a word and a non-word character and therefore matches C++ inside C++11 while refusing café before a comma. This definition does neither.

Why does the tool say a pattern is invalid instead of just finding nothing?

Because those are different facts and only one of them is your fault. An unterminated character class is a typo you want to know about; a pattern that matched nothing is an answer. The previous version reported both as zero matches with no message.

Can I limit the replacement to one column?

Yes. Select a specific column from the dropdown, or leave it on All Columns. The rules list under the result says which column was searched, counting from one.

Does it change my header row?

Not unless you turn on "Search the header row". By default the header is left alone and the tool says so, because renaming a column is a different job from cleaning the data under it.

Private by design

Rows and columns are parsed and transformed in memory in your browser. No record ever reaches a server.