135

I'm looking for the best way to do search-and-replace (with confirmation) across all project files in Vim. By "project files" I mean files in the current directory, some of which do not have to be open.

One way to do this could be to simply open all of the files in the current directory:

:args ./**

and then do the search and replace on all open files:

:argdo %s/Search/Replace/gce

However, when I do this, Vim's memory usage jumps from a couple dozen of MB to over 2 GB, which doesn't work for me.

I also have the EasyGrep plugin installed, but it almost never works—either it doesn't find all the occurrences, or it just hangs until I press CtrlC. So far my preferred way to accomplish this task it to ack-grep for the search term, using it's quickfix window open any file that contains the term and was not opened before, and finally :bufdo %s/Search/Replace/gce.

I'm looking either for a good, working plugin that can be used for this, or alternatively a command/sequence of commands that would be easier than the one I'm using now.

Keith Pinson
  • 7,835
  • 7
  • 61
  • 104
psyho
  • 7,142
  • 5
  • 23
  • 24

13 Answers13

116

The other big option here is simply not to use vim:

sed -i 's/pattern/replacement/' <files>

or if you have some way of generating a list of files, perhaps something like this:

find . -name *.cpp | xargs sed -i 's/pattern/replacement/'
grep -rl 'pattern1' | xargs sed -i 's/pattern2/replacement/'

and so on!

Cascabel
  • 479,068
  • 72
  • 370
  • 318
  • Thanks! I really need to learn this old school command line stuff. I think this is the best answer. Are these regexes perl regexes or vim regexes or some other kind of regex? – Eric Johnson Jan 05 '12 at 09:27
  • 2
    @EricJohnson: They're... `sed` regexes, which are essentially POSIX.2 basic regular expressions, but not exactly (this is in the manpage) - they let `\n` match newlines, and some other similar things. They're therefore pretty much the same as `grep` regular expressions. – Cascabel Jan 05 '12 at 13:17
  • Is there a reason you have -i in the sed command? The man page says that is for specifying an extension, but you don't appear to actually specify one. – davekaro Feb 23 '13 at 13:26
  • Ok it actually looks like 's/pattern/replacement/' is the extension, I think I understand now. – davekaro Feb 23 '13 at 13:29
  • @davekaro Sorry, missed those comments before. I think you've misread the manpage. `-i` means "in place" - it makes sed change the contents of the files instead of printing the result to stdout. If you provide an argument for it, it's used as a file extension for backup files (in place actually means write to backup file then move that over the original). But `sed -i 's/.../.../'` is not parsed as an argument for `-i` on any system I"ve used. The `'s/.../.../` is just the command. – Cascabel Nov 03 '13 at 17:36
  • 15
    On Mac OS X the only sed command that worked for me was: `find . -type f | LANG=C xargs sed -i '' 's/SEARCH/REPLACE/g'` – ghodss Sep 15 '15 at 07:39
  • In case your file names contain spaces, you can add another `sed` transformation that replaces beginning and end of lines with double quotes (thus, making the names safe to pass along) like so: `grep -rl 'pattern1' | sed -e 's/^/"/g' -e 's/$/"/g' | xargs sed -i 's/pattern2/replacement/'` – rpyzh Jul 12 '17 at 20:04
  • I would rate this as the best command I ever ran on Unix - simply brilliant: sed -i 's/pattern/replacement/' * – Kapil Vyas Mar 19 '18 at 23:07
90

EDIT: Use cfdo command instead of cdo to significantly reduce the amount of commands that will be run to accomplish this (because cdo runs commands on each element while cfdo runs commands on each file)

Thanks to the recently added cdo command, you can now do this in two simple commands using whatever grep tool you have installed. No extra plugins required!:

1. :grep <search term>
2. :cdo %s/<search term>/<replace term>/gc
3. (If you want to save the changes in all files) :cdo update

(cdo executes the given command to each term in the quickfix list, which your grep command populates.)

(Remove the c at the end of the 2nd command if you want to replace each search term without confirming each time)

Sid
  • 2,683
  • 18
  • 22
  • 13
    Plus you can add ````:cdo update```` to save the changes in all files. – Zhe Li Jul 08 '16 at 06:43
  • 1
    But it will pauses to warn not saved each time before switching to the next buffer when current buffer is modified. – lfree May 27 '17 at 02:09
  • 1
    @Zhe thanks, I've added that to the answer; @lfree I personally prefer confirming each change, but you can remove the `c` at the end of the second command (just use `g`) to have it search and replace without asking for confirmation – Sid Jun 19 '17 at 06:08
  • For `cdo`, the range `%` is not necessary. It's needed only when using `cfdo`. – Michal Čizmazia Feb 15 '18 at 11:14
  • This should be the new answer. Works perfectly and no need for plugins. – Emobe Feb 16 '18 at 10:52
  • 2
    :cdo %s/search term/replace term/g only replace the first occurence, but does not work for the second occurrence in my vim – sunxd Sep 28 '18 at 16:36
  • Since command 2. above (running `cdo`) depends on the correct `grep` command to populate the quickfix list, it may help to ensure that the `grep` command produced desired output by looking at the quickfix window (*tip:* `:copen`). – HelloWorld101 May 08 '19 at 13:55
  • Is there a way to automatically save the file after the search and replace? I run into the same problem as @lfree – jhnatr Jun 26 '19 at 15:45
  • 5
    for using `update`, I had to: `cdo %s///g | update` – gabe Sep 18 '19 at 00:47
  • 1
    I recommend doing `:cdo .s/` so that you only replace the lines returned by the grep. Otherwise if you don't want to do a match you need to say `n` to it once for each match in the file. If you want to do a global search on the file you can do `:cfdo %s/` so that you search and replace globally but it only ones once-per-file. – Kevin Cox Dec 13 '19 at 13:21
  • @KevinCox I didn't understand your comment until I attempted to do this myself. using %s instead of .s will not only try to match the line from the quickfix list, once the file is open, it will globally match every instance in that file. Once that is finished, it picks up the next line from the quickfix list. And the cycle starts again, so if you can imagine this being problematic if you had 10 matches in one file and only wanted to change two of them. You would search that same file at LEAST 10 times – Jay M Feb 24 '20 at 13:26
  • @KevinCox thanks I updated the answer to reflect this – Sid Feb 29 '20 at 00:03
  • @Sid Thanks. Why not just replace the command in the answer though? I don't see why you would prefer `:cdo %` over `:cfdo %` or `:cdo .` – Kevin Cox Feb 29 '20 at 09:25
  • This should be the accepted answer. I used :cdfo in neovim and replaced a string in about 25 files in a single project with minimal amounts of messing around with opening and closing buffers required from my part. Any no additional plugins required! – philbert Oct 19 '20 at 15:12
  • This is definitely the best answer. Should definitely be accepted. – Kirn Oct 03 '22 at 19:40
35

I've decided to use ack and Perl to solve this problem in order to take advantage of the more powerful full Perl regular expressions rather than the GNU subset.

ack -l 'pattern' | xargs perl -pi -E 's/pattern/replacement/g'

Explanation

ack

ack is an awesome command line tool that is a mix of grep, find, and full Perl regular expressions (not just the GNU subset). Its written in pure Perl, its fast, it has syntax highlighting, works on Windows and its friendlier to programmers than the traditional command line tools. Install it on Ubuntu with sudo apt-get install ack-grep.

xargs

Xargs is an old unix command line tool. It reads items from standard input and executes the command specified followed by the items read for standard input. So basically the list of files generated by ack are being appended to the end of the perl -pi -E 's/pattern/replacemnt/g' command.

perl -pi

Perl is a programming language. The -p option causes Perl to create a loop around your program which iterates over filename arguments. The -i option causes Perl to edit the file in place. You can modify this to create backups. The -E option causes Perl to execute the one line of code specified as the program. In our case the program is just a Perl regex substitution. For more information on Perl command line options perldoc perlrun. For more information on Perl see http://www.perl.org/.

Eric Johnson
  • 17,502
  • 10
  • 52
  • 59
  • I like this answer because it works even with cygwin's line endings. Note that it does add a \r to the end of modified lines, but when using sed, it will replace *every* newline, making your diffs unusable. Perl is a bit more sane, and this is a really great one-liner for search and replace. Thanks! – andrew Mar 22 '12 at 04:50
  • @andrew, I'm not sure exactly whats going wrong in your case. Would it help you to use \R instead of \n in your regexps? See \R section in http://perldoc.perl.org/perlrebackslash.html#Misc. Also try http://perldoc.perl.org/perlre.html#Regular-Expressions. Perl is extremely cross platform and I'm sure there is a way for you to not end up with mangled line endings. – Eric Johnson Mar 23 '12 at 10:01
  • My regex doesn't have any newlines in it, the cygwin versions of these programs automatically add \r at the end of each line modified. I specifically liked the perl version because it only modifies the lines it matches whereas sed will modify *all* lines, even if they don't match the regex. – andrew Mar 23 '12 at 19:27
  • What if the file's path contains spaces? – shengy Jun 06 '14 at 03:40
27

Greplace works well for me.

There's also a pathogen ready version on github.

nelstrom
  • 18,802
  • 13
  • 54
  • 70
tuxcanfly
  • 2,494
  • 1
  • 20
  • 18
  • 8
    Installed it, I see the other commands like `:Gsearch` and `:Gbuffersearch` exist, but when I type `:Greplace` I get `Not an editor command: Greplace`. – Ramon Tayag Jul 02 '11 at 08:46
  • according to the docs, the syntax is: `:Gsearch [] [[] []]` so you could do a recursive search using, for example: `:Gsearch -r pattern dir/` – vitorbal Jun 26 '13 at 10:08
  • The thing I hate about Greplace is that if the pattern is in the filename, Greplace fails, because you end up replacing it in the filename, too. – Daniel Apr 16 '15 at 15:04
  • 4
    Nothing unusual here after 6 years, but this plugin is severely outdated in comparison to some new answers (https://github.com/yegappan/greplace) last update, 3+ years ago. I might check out the builtin solution by Sid: http://stackoverflow.com/a/38004355/2224331 (It's not me on another account, just a coincidence :P) – SidOfc May 12 '17 at 11:08
24

maybe do this:

:noautocmd vim /Search/ **/*
:set hidden
:cfirst
qa
:%s//Replace/gce
:cnf
q
1000@a
:wa

Explanation:

  • :noautocmd vim /Search/ **/* ⇒ lookup (vim is an abbreviation for vimgrep) pattern in all files in all subdirectories of the cwd without triggering autocmds (:noautocmd), for speed's sake.
  • :set hidden ⇒ allow having modified buffers not displayed in a window (could be in your vimrc)
  • :cfirst ⇒ jump to first search result
  • qa ⇒ start recording a macro into register a
  • :%s//Replace/gce ⇒ replace all occurrences of the last search pattern (still /Search/ at that time) with Replace:
    • several times on a same line (g flag)
    • with user confirmation (c flag)
    • without error if no pattern found (e flag)
  • :cnf ⇒ jump to next file in the list created by the vim command
  • q ⇒ stop recording macro
  • 1000@a ⇒ play macro stored in register a 1000 times
  • :wa ⇒ save all modified buffers

* EDIT * Vim 8 way:

Starting with Vim 8 there is a better way to do it, as :cfdo iterates on all files in the quickfix list:

:noautocmd vim /Search/ **/*
:set hidden
:cfdo %s//Replace/gce
:wa
Benoit
  • 76,634
  • 23
  • 210
  • 236
  • you might want to explaing that a bit further for us lesser mortals. It's not clear to me if you need to do this "sequence" every time. I'm faster doing that using my rusty old Delphi 5 so surely I must be missing something. – Lieven Keersmaekers Jan 27 '11 at 08:01
  • 1
    @Lieven: You are right, this is a bit obscure without comments. I will develop some explanation. – Benoit Jan 27 '11 at 08:05
  • +1 but although `vim` has won me over for many things, I think I'll stick to PowerGrep with this. – Lieven Keersmaekers Jan 27 '11 at 09:58
  • Thanks for your answer and the explanation of all of the commands, I really appreciate it. I'm accepting the other answer because I prefer to use a plugin for this. – psyho Jan 27 '11 at 10:25
  • I've marked this down because it causes more confusion, given the disparity between the level of someone asking the question and the level required to understand this answer. – Lloyd Moore Apr 09 '13 at 13:16
  • This is the right answer because the question literally says "in vim" in the title. – David Rivers Jun 20 '13 at 14:36
  • Good answer, this is in vim and you can create your own command in vimrc with all those lines to simplify. – javier_domenech Mar 11 '15 at 13:09
23

Populate :args from a shell command

It's possible (on some operating systems1)) to supply the files for :args via a shell command.

For example, if you have ack2 installed,

:args `ack -l pattern`

will ask ack to return a list of files containing 'pattern' and put these on the argument list.

Or with plain ol' grep i guess it'd be:

:args `grep -lr pattern .`  


You can then just use :argdo as described by the OP:

:argdo %s/pattern/replacement/gce


Populate :args from the quickfix list

Also check out nelstrom's answer to a related question describing a simple user defined command that populates the arglist from the current quickfix list. This works great with many commands and plugins whose output ends up in the quickfix list (:vimgrep, :Ack3, :Ggrep4).

The sequence to perform a project wide search could then be done with:

:vimgrep /pattern/ **/*
:Qargs 
:argdo %s/findme/replacement/gc

where :Qargs is the call to the user defined command that populates the arglist from the quickfix list.

You'll also find links in the ensuing discussion to simple plugins that get this workflow down to 2 or 3 commands.

Links

  1. :h {arglist} - vimdoc.sourceforge.net/htmldoc/editing.html#{arglist}
  2. ack - betterthangrep.com/
  3. ack.vim - github.com/mileszs/ack.vim
  4. fugitive - github.com/tpope/vim-fugitive
Community
  • 1
  • 1
exbinary
  • 1,086
  • 6
  • 8
9

One more option in 2016, far.vim plugin:

far.vom

Oleg Khalidov
  • 5,108
  • 1
  • 28
  • 29
6
1. :grep <search term> (or whatever you use to populate the quickfix window)
2. :cfdo %s/<search term>/<replace term>/g | update

Step 1 populates the quickfix list with items you want. In this case, it's propagated with search terms you want to change via grep.

cfdo runs the command following on each file in the quickfix list. Type :help cfdo for details.

s/<search term>/<replace term>/g replaces each term. /g means replace every occurrence in the file.

| update saves the file after every replace.

I pieced this together based upon this answer and its comments, but felt it deserved its own answer since it's all in one place.

Kyle Heironimus
  • 7,741
  • 7
  • 39
  • 51
  • For me on NeoVim, a slight modification needed to be done in the line 2 above: `2. :cfdo %s///g | update` Without the `%`, only the first occurrence of the pattern is replaced. – Kareem Ergawy May 28 '20 at 08:51
  • Thanks Kareem. I updated the answer since `%s` works on regular vim too. – Kyle Heironimus May 28 '20 at 13:08
5

If you don't mind of introducing external dependency, I have brewed a plugin ctrlsf.vim (depends on ack or ag) to do the job.

It can format and display search result from ack/ag, and synchronize your changes in result buffer to actual files on disk.

Maybe following demo explains more

ctrlsf_edit_demo

dyng
  • 2,854
  • 21
  • 24
3
  1. Make sure you’re using Neovim (or Vim 7.4.8+, but really just use Neovim)
  2. Install FZF for the command line and as a vim plugin
  3. Install Ag, so that it’s available automatically to FZF in vim
  4. If using iTerm2 on OSX, set the alt/option key to Esc+

Usage

Search the text you want to change in the current directory and it’s children with

:Ag text

  • Keep typing to fuzzy filter items
  • Select items with alt-a
  • Deselect items with alt-d
  • Enter will populate the quickfix list
  • :cfdo %s/text/newText/g | :w

Now you have chabges made inside Vim NeoVim

source

Andres Felipe
  • 4,292
  • 1
  • 24
  • 41
1

You can do it with shell and vim's ex-mode. This has the added benefit of not needing to memorize other search-and-replace escape sequences.

Any command that can list files will work (rg -l, grep -rl, fd...). For example in bash:

for f in $(rg -l Search); do
vim -Nes "$f" <<EOF
%s/Search/Replace/g
wq
EOF
done

You can use any command, those prefixed with : in command mode, the same way you would inside vim, just drop the : at the start

Voovs
  • 11
  • 1
1

I think that is because you have a huge amount of files been captured in memory. For my case, I do this by group files in different types, for example:

:args **/*.md
argdo <command>

:args **/*.haml
argdo <command>
hiveer
  • 660
  • 7
  • 17
0

Basically, I wanted the replace in a single command and more importantly within vim itself

Based on the answer by @Jefromi i've created a keyboard shortcut, which I had set in my .vimrc file like this

nmap <leader>r :!grep -r -l  * \| xargs sed -i -e 's///g'

now from the vim, on a stroke of leader+r I get the command loaded in vim, which i edit like below,

:!grep -r -l <find> <file pattern> | xargs sed -i -e 's/<find>/<replace>/g'

Hence, I do the replace with a single command and more importantly within vim itself

Community
  • 1
  • 1
deepak
  • 3,074
  • 2
  • 20
  • 29