34

One of the open questions I have about Vim is if there is a way to perform a search/replace in the current project (bear with me if I use this notion inherited from other editors).

For instance, let's assume I want to search my project files for a method name, and rename all the instances of that method call.

This is how I can proceed with Sublime.

enter image description here

How would you do this in Vim (possibly MacVim) without relying on other programs such as ack or sed?

29

There are several ways to do this.

Inside Current Directory

If you want to perform the search/replace in a project tree, you can use Vim's argument list.

Simply open Vim and then use the :args command to populate the argument list. You can pass in multiple filenames or even globs.

For example, :args **/*.rb will recursively search the current directory for ruby files. Notice that this is also like opening Vim with vim **/*.rb. You can even use the shell's find command to get a list of all files in the current directory by running:

:args `find . -type f`

You can view the current args list by running :args by itself. If you want to add or delete files from the list, you can use the :argadd or the :argdelete commands respectively.

Once you're happy with the list, now you can use Vim's powerful :argdo command which runs a command for every file in the argument list: :argdo %s/search/replace/g

Here are some tips for searching (based on some of the comments):

  1. Use a word boundary if you wanted to search for "foo" but not "foo_bar". Use the \< and \> constructs around the search pattern like so: :argdo %s/\<search\>/foobar/g
  2. Use a /c search flag if you want Vim to ask for confirmation before replacing a search term.
  3. Use a /e search flag if you want to skip the "pattern not found" errors.
  4. You can also choose to save the file after performing the search: :argdo %s/search/replace/g | update. Here, :update is used because it will only save the file if it has changed.

Open buffers

If you already have buffers open you want to do the search/replace on, you can use :bufdo, which runs a command for every file in your buffer list (:ls).

The command is very similar to :argdo: :bufdo %s/search/replace/g

Similar to :argdo and :bufdo, there is :windo and :tabdo that act on windows and tabs respectively. They are less often used but still useful to know.

  • 4
    It could probably be a good idea to use the /c flag for confirming substitutions. – romainl Mar 29 '15 at 11:20
  • 3
    I would also recommend using word boundaries \< and \> around the search to avoid partial word replacements. – tommcdo Mar 29 '15 at 14:02
  • 1
    One other comment is that vim's working directory is by default where you started vim, not the current file's directory. You can set the working directory to the current file using :cd %:p:h. Other working directory options are listed in vim.wikia.com/wiki/Set_working_directory_to_the_current_file. – studgeek Jul 21 '16 at 16:37
22

Let's say we have a simple project structure like this:

enter image description here

greeting.txt looks like

enter image description here

and info/age.txt looks like

enter image description here

Let's say we want to replace all occurences of Sam with Bob. Here's what we would do:

  1. Set working directory

    Make sure Vim's current working directory is the root of the project (won't show you how to do this here).

  2. Find files that contain 'Sam'

    Use Vim's vimgrep command to search for all occurences of Sam within the project (note: the **/* searches in all files recursively)

    :vimgrep /Sam/gj **/*
    

    This will populate the quickfix list with all instances of Sam. If you want to view the quickfix list, you can use the Vim command :copen enter image description here

  3. Substitute within all files that contain 'Sam'

    Now we want to run Vim's substitute command inside every file in the quickfix list. We can do this using the :cfdo {cmd} command which executes {cmd} in each file in the quickfix list. The specific {cmd} we want to use is substitute or s for short. The full line would look like:

    :cfdo %s/Sam/Bob/g
    

    You can add the c flag to confirm all replacements if you like:

    :cfdo %s/Sam/Bob/gc
    
  4. Save all files

    :cfdo update
    
  • 2
    I like this more. It's simple, and works with Ack (or any plugin that populates the QuickFix window). – Zuhaib Nov 28 '16 at 16:07
  • 1
    This is a great answer – Peoplee Dec 7 '16 at 17:21
  • 2
    Was getting "no write since last change", doing cfdo %s/Sam/Bob/g | update fixed that for me – hakunin Dec 14 '17 at 11:14
  • @hakunin I'd recommend setting the hidden option with :set hidden if you haven't already done so. – Niko Bellic Jan 8 '18 at 22:57
3

Although not a VIM solution, if you are using VI/VIM you might find it easier to use a CLI solution. To change all files in the current directory, use perl as it was made for situations like this:

$ perl -pi -e 's/foo/bar/g' *

If you need to do it in subdirectories too, I usually pair perl with find:

$ find . -name '*' -print0 | xargs -0 perl -pi -e 's/foo/bar/g'

The nice thing about this is that you can easily further refine the list to specific file types. For instance, to restrict to python files:

$ perl -pi -e 's/foo/bar/g' *.py
$ find . -name '*.py' -print0 | xargs -0 perl -pi -e 's/foo/bar/g'
1

To replace phrase with another one, you can use Vim Ex mode.

For example (ex is alias to vim -E):

$ ex -s +'bufdo!%s/foo/bar/g' -cxa *.*

Recursively in bash/zsh (with globstar set, e.g. shopt -s globstar):

$ ex -s +'bufdo!%s/foo/bar/g' -cxa **/*.*
$ ex -s +'n **/*.*' +'bufdo!%s/foo/bar/g' -cxa

Recursively (using find and ignore hidden files such as .git files):

$ find . -type f -not -path '*/\.*' -exec ex -s +'bufdo!%s/foo/bar/g' -cxa {} "+"

Note: The :bufdo command is not POSIX.

Note: Syntax -not -path '*/\.*' is used to ignore hidden files (such as .git).

0

Here is the shell command using find and ex editor (without using :bufdo):

find . -name '*.rb' -exec ex +'%s/foo/bar/ge' -V1 -scwq! {} ';'

This will edit in-place all *.rb files in the current folder (recursively) and replace foo with bar.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.