92

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?

6 Answers 6

99

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.

3
  • 6
    It could probably be a good idea to use the /c flag for confirming substitutions.
    – romainl
    Mar 29, 2015 at 11:20
  • 5
    I would also recommend using word boundaries \< and \> around the search to avoid partial word replacements.
    – tommcdo
    Mar 29, 2015 at 14:02
  • 3
    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, 2016 at 16:37
97

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 occurrences 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:

     :cd {path to root directory}

You can use :pwd to print the current working directory and ensure that it is correct.

2. Find files that contain 'Sam'

Use Vim's :vimgrep command to search for all occurrences of Sam within the project:

    :vimgrep /Sam/gj **/*

Notes:

  • Sam is the search "pattern" sandwiched between two forward slashes
  • The **/* says to search in all files recursively
  • The g flag says to search for all occurrences in each line (this is actually overkill here, but it does not hurt either)
  • The j flag prevents vim from automatically jumping to the first match

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. Adding the update command at the end ensures that each file is saved before moving on to the next one (this is necessary if you don't :set hidden). The full line would look like:

    :cfdo %s/Sam/Bob/gc | update

If you do :set hidden, you can optionally leave off | update, and then do :cfdo update or :wall.

Notes:

  • The % is a line range that specifies every line
  • The g flag says to substitute all occurrences in each line
  • The c flag causes vim to ask you to confirm each replacement individually (you might want to leave this out)
6
  • 4
    I like this more. It's simple, and works with Ack (or any plugin that populates the QuickFix window).
    – Zuhaib
    Nov 28, 2016 at 16:07
  • 2
    This is a great answer
    – Peoplee
    Dec 7, 2016 at 17:21
  • 7
    Was getting "no write since last change", doing cfdo %s/Sam/Bob/g | update fixed that for me
    – hakunin
    Dec 14, 2017 at 11:14
  • 2
    @hakunin I'd recommend setting the hidden option with :set hidden if you haven't already done so. Jan 8, 2018 at 22:57
  • This is more portable answer, works in any OS.
    – SdSaati
    Jun 10, 2020 at 23:10
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).

1
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.

0

Base on https://vi.stackexchange.com/a/10310 enter image description here

    com!  -nargs=+   Fr    call Find_replacE(<f-args>)
        fun! Find_replacE(old_s, new_s)
            execute 'vimgrep' '@'..a:old_s..'@gj' '**/*'
            execute 'cfdo % sub@'..a:old_s..'@'..a:new_s..'@gc  | update '
        endf
1
  • 1
    Instead of hardcoding the @ separator, consider using the escape function to escape the separator or taking the pattern with delimiters
    – D. Ben Knoble
    May 28, 2022 at 20:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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