Taipei, 2017-01-10 10:22:53 +0800
I wrote this post as a tutorial for my colleague who also uses Vim as his primary text editor.

To learn how to perform search and replace in Vim, we first need a code sample. If you don't have a project to practise on, you can start by cloning into this COBOL Hello World repository from Github:

git clone https://github.com/now-examples/cobol-hello-world/

A common way to perform project-wide search and replace in Vim is using quickfix list. To use this functionality in classic Vim, you need to make sure your copy of Vim is compiled with the quickfix option. You can check it by running:

vim --version | grep quickfix

If your copy of Vim supports quickfix, the output should include +quickfix (a minus means that the option was not enabled in compilation):

+cursorbind      +listcmds        +quickfix        +wildmenu

If your Vim binary has no quickfix support, try installing a different package or compiling it from source. You can also consider switching to Neovim, an improved, modernised fork of Vim, which supports quickfix by default.

The first step to perform project-wide search and replace in Vim is to search for a pattern. To this end, you can use Vim's built-in command :vimgrep. If you have ag (a.k.a. the Silver Searcher) installed on your system, you can configure Vim to use it instead of grep. Add these lines to your ~/.vimrc (or ~/.config/nvim/init.vim on Neovim)1:

if executable('ag')
    " Note we extract the column as well as the file and line number
    set grepprg=ag\ --nogroup\ --nocolor\ --column
    set grepformat=%f:%l:%c%m
endif

Save the file with :w and source it with :source %.

Suppose we want to replace all occurences of identification division in the COBOL Hello World Project with "chunky bacon". First, let's perform a project-wide search:

:vimg 'identification division' **

The command vimgrep takes two parameters, a search pattern (if it contains spaces or special characters, you have to enclose it in quote marks,) and the search path. If you use ag, you don't have to specify the search path (ag will search recursively through all files in current directory, ignoring files specified in the project's .gitignore or .agignore files):

:grep 'identification division'

Both vimgrep and ag populate the quickfix list with search results. You can then see the list using :copen:

[[Image:203_quickfix_list.png]]

The general syntax for performing a file-wide search and replace in Vim is as follows:

:%s/procedure division/spam and eggs/g

The part of the command before s denotes a range in the file, % meaning all lines in the current file2. Then come the search and replace patterns delimited by arbitrary separators, usually slashes or colons. You can use regular expressions in the search pattern. Keep in mind, however, that Vim's regex syntax differs significantly from the most widespread Perl-like syntax. The g flag at the end tells Vim to substitute all occurrences of the search pattern in each line, rather than only the first occurence in the line. Another flags include c, which will ask you to confirm each substitution, and i, which makes the search pattern case-insensitive.

We can invoke the substitution command for several files using :argdo, first, however, we need to populate the args list with all files in the quickfix list. To do this, we can use the Qargs command3. Add these lines to your .vimrc or init.vim:

command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
function! QuickfixFilenames()
  " Building a hash ensures we get each buffer only once
  let buffer_numbers = {}
  for quickfix_item in getqflist()
    let bufnr = quickfix_item['bufnr']
    " Lines without files will appear as bufnr=0
    if bufnr > 0
      let buffer_numbers[bufnr] = bufname(bufnr)
    endif
  endfor
  return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
endfunction

Remember to save the file with :w and source it with :source %. If you use :Qargs now, your argument list should be populated with vimgrep's or ag's search results. You can verify it using the command :args. Now let's get the actual job done:

:argdo %s/identification division/chunky bacon/g

All occurrences of identification division in the project have been substituted with chunky bacon! To save the results, use:

:argdo w

or

:argdo update


  1. Winterbottom D., Using the silver searcher with Vim, codeinthehole.com. Retrieved 10th Dec., 2017.

  2. Read more on ranges on Vim Tips Wiki – Ranges. Retrieved 10th Dec., 2017.

  3. See nelstrom's answer to this question on Stack Overflow. Retrieved 10th Dec., 2017.