puddles
puddles

Reputation: 103

In Vim, how do you repeat multiple substitutions for multiple files?

I have 10 files, each containing 'apple' and 'red'. I want to change 'apple' to 'pear' and change 'red' to 'green' across all 10 files.

My current method is typing out this find and replace command :%s/apple/pear/g (and the same for red to green) in each file. How do I save these two substitutions and have them apply to the 10 files. More generally, how do I do this more efficiently?

I'm new to vim and am not sure whether to look into command history or vim register or something else.

Upvotes: 2

Views: 2659

Answers (3)

idbrii
idbrii

Reputation: 11916

The quickfix-reflector allows you to edit results in the quickfix and apply those edits back to the original files.

  1. Use :grep as usual to search your project for matches (:grep apple -R .) -- or whatever method you want to populate the quickfix.
  2. Edit the quickfix. (:%s/apple/pear/ge | %s/red/green/ge)
  3. Save the quickfix buffer. (:w)
  4. Now all your files are modified (or the quickfix shows ERROR next to lines that it couldn't update).

Normally, the quickfix isn't 'modifiable', but quickfix-reflector will modify that setting and add a hook to propagate changes on save. There are several alternative plugins offering similar functionality and it's a big change to your workflow!

Upvotes: 2

Peter Rincker
Peter Rincker

Reputation: 45117

The basics

Vim does not come with a project-wide find & replace. It comes down to doing the following steps:

  • Create a list of files you wish to work upon
  • Run your command on each file on the list

The substitution

You can run multiple commands one after another with the |.

Example

:s/apple/pear/ | s/red/green/

Since you updating these files it might be best to also :write or :update the file as well

:s/apple/pear/ | s/red/green/ | update

Substitution flags:

  • Use g for "global" or doing multiple substitutions per line
  • Use e to suppress errors. We need this as a file may have apple but no red for example

:s takes a range like 1,3 to work on lines 1 through line 3. $ represents the last line number. An entire file is 1,$. Use % as shorthand for 1,$

:%s/apple/pear/ge | %s/red/green/ge | update

Thoughts:

  • May want to use \< & \> around your patterns. E.g. red pattern as red can be in different words like predicate
  • May want to take case into consideration

Argument List

If you started Vim with the list of files you wish to work on then these files are already in the Argument List

$ vim file*.txt

You can supply arguments after vim is started via :args or :argadd. e.g. :args file*.txt

Use :args to see the argument list

Use :argdo {cmd} to run your command, {cmd} over all files in the argument list

Quickfix List

Often you want to search for a pattern in a group of files and then do a replacement on those matches. Vim's :vimgrep & :grep search for files and put these positions into the Quickfix List

:vimgrep /apple\|red/ **/*.txt

You can see the quickfix list with :copen or :clist.

Use :cdo {cmd}/:cfdo {cmd} to run a command, {cmd}, over every position/file in the quickfix list

:cfdo %s/apple/pear/ge | %s/red/green/ge | update
:cdo s/apple/pear/e | s/apple/pear/e | update

Leveling up :grep

:grep will use 'grepprg' & 'grepformat' to execute and read the output of program like grep (used by default on linux). This is often faster than :vimgrep at the expense of using a different regex syntax.

Instead of using grep as your 'grepprg', you can use something like ripgrep or ag the silver searcher which are both often even faster than plain grep.

Example of config options for ripgrep:

set grepprg=rg\ --vimgrep
set grepformat=%f:%l:%c:%m

Now you can do :grep 'apple\|red' to populate the quickfix list

You can even take this further if you want by running :grep in a subshell

Other lists?!?

Vim has other lists like: buffer, window, tabs, location list. These have an associated "do" command with them: :bufdo, :windo, :tabdo, :ldo

Abolish's :Subvert

Although, no plugins are needed to do such substitutions across files, it might be handy to know about Tim Pope's vim-abolish plugin. Especially if you have many of these substitutions and/or have casing issues. Abolish's :Subvert/:S command can reduce your substitution commands to 1 command

:%S/{apple,red}/{pear,green}/gw

This will convert apple -> pear and red -> green due to their positions inside of the curly braces.

Use :Subvert's w flag to only substitute words. Subvert also will handle cases

Vimcasts

Here are some Vimcasts episodes which are related:

TL;DR

Search with :vimgrep and run your substitutions with :cfdo

:vimgrep /apple\/red/ *.txt
:cfdo %s/apple/pear/ge | %s/red/green/ge | update

For more help

Read more using Vim's help system

   :h argument-list
   :h :argdo
   :h :args
   :h quickfix.txt
   :h :cdo
   :h :cfdo
   :h :vimgrep
   :h :grep
   :h 'grepprg'
   :h 'grepformat'

Upvotes: 11

filbranden
filbranden

Reputation: 8898

You can use a command such as :argdo or :bufdo to repeat the same Ex command on many files. (See :help :argdo.)

For example, you can open your 10 files by passing them as arguments to Vim. If they're all *.txt files, you can use:

$ vim *.txt

Then you can use :argdo to repeat the operation on all the files Vim got as arguments:

:argdo %s/apple/pear/eg | update

Of notice here, I'm adding a | update at the end of the operation, in order to save the file after performing the substitution, for each of the files. That's important, otherwise :argdo will error out before moving to the next file. (See :help :update, it's similar to :w but only saves when there are modifications. The | is a command separator for Ex commands in Vim.)

An alternative to using | update is to use :set hidden, which allows you to start editing another file before moving on to the next one. (See :help 'hidden'.) You can later use :wall to write all files once you're done with the modifications everywhere.

Finally, I'm passing an extra /e flag to the substitution, to prevent it from generating an error if there is no match to replace. (See :help :s_e.)

You can also chain multiple commands (such as substitution commands) by using | as a separator. For example:

:set hidden
:argdo %s/apple/pear/eg | %s/red/green/eg
:wall

Upvotes: 2

Related Questions