开发者

Interactive search and replace from shell

Search and repl开发者_如何学运维ace over multiple files is difficult in my editor. There are plenty of tricks that can be done with find, xargs and sed/awk incluing search-and replace in multiple files. But somehow I couldn't find a way to make this interactive. Do you know a way to do that?


KISS principle:

vim
:args `ls`
:argdo %s#SEARCH#REPLACE#gec |update

First character afer %s is used as separator


I would add these modifications to Dillon's answer:

The -le option should be added to the grep command.

vim `find . -name '*.c' -exec grep -le '\<junk\>'  {} \;`

Then you are in Vim, but you don't have the opportunity to choose what to replace, add c option at the end for interactive replacements and bufdo at the beginning for walking through every file:

:bufdo %s/junk/rubbish/gce

Later you save all your work:

:bufdo wq!


From jhvaras answer, I've made this bash command to quickly search and replace (to add to .bashrc):

replace () {
    if [ $# -lt 2 ]
    then
        echo "Recursive, interactive text replacement"
        echo "Usage: replace text replacement"
        return
    fi

    vim -u NONE -c ":execute ':argdo %s/$1/$2/gc | update' | :q" $(ag $1 -l)
}

It's used as follows:

~$ replace some_text some_new_text

It uses ag to search in advance, as it's probably faster than letting vim do the work, but you can probably substitute anything else you like. It also calls vim with no plugins for maximum speed, and after it has finished all substitutions it automatically quits and goes back to the shell.


Just use Vim.

Start by using find to make a list of all files which need changing like so.

vim `find . -name '*.c' -exec grep junk {} \;`

That starts vim with all the .c files containing the string junk. Now, in vim make your change to the first file:

:%s/junk/rubbish/g

And then type :wEnter:nEnter for the next file.

Now you need to repeat the editing process. If it is only one substitute command and just a few files, then type :UpEnter to repeat the substitute command.

But if there are a lot, you should use map to create a couple of kestroke macros. The first macro would do the substitute command(s) and the second macro would do the w and n commands to save changes and get the next file.

I have used this (and older variants with vi) for the past 30 years. In particular, when you encode the two parts of this as two keystroke macros, you can run it very fast because it is easy to hold down the control key, and type a couple of letters, for instance R Y, over and over again. It is faster than the time it takes to write a fully automated script and since this is simple to reproduce, you can do it on any machine that you happen to be working on.


A UNIX way to do this with grep, xargs, and vi/vim:

  1. Make an alias

This will help avoiding long commands when you want to exclude certain things from grep.

alias ge='grep --exclude=tags --exclude=TAGS --exclude-dir=.git --exclude-dir=build --exclude-dir=Build --exclude-dir=classes --exclude-dir=target--exclude-dir=Libraries --exclude=*.log --exclude=*~ --exclude=*.min.js -rOInE $*'
  1. Perform the search and check your file list

Command:

ge -l 'foo' .
  1. Perform the search again, telling vim to replace interactively on each file

Command:

ge -l 'foo' . | xargs -L 1 -o vim -c '%s/foo/bar/gc'

Explanation of the command:

  • The part till the pipe is the search command repeated

  • After the pipe, we call xargs to launch vim on each file

  • "-L 1" tells xargs to launch the command on each line instead of combining all lines in a single command. If you prefer to launch vim on all files at once, remove "-L 1" from xargs.

  • "-o" tells xargs to use the actual terminal rather than /dev/null

You have two options for this command:

a) Launch vim on all files at once

Advantages:

  • Command is easy to cancel: just quit vim

Disadvantages:

  • You have to switch to each buffer in turn and re-run the "%s" command on it

  • If you have a lot of files, this can make vim consume a lot of memory (sometimes as much as a full-blown IDE!)

b) Launch vim in turn for each file

Advantages:

  • Everything is automated. Vim is launched and performs the search command

  • Light on memory

Disadvantages:

  • It's hard to cancel: quitting vim will just make xargs open the next one

I prefer variant b) as it's automated and more UNIX-like. With careful planification - inspecting the files list first, and maybe running the command on sub-directories to reduce scope - it is very nice to use.

General advantages:

  • It can be achieved with standard UNIX tools, available on virtually every platform

  • It's nice to use

General disadvantages:

  • You have to type the search expression twice

  • You might hit issues with bash escaping on complex search/replace expressions


Here is an enhancement of @jhvaras's answer if you find that you frequently want to do this, or if you only want to search files under version control.

function SvnProjectSubstitute(replacement)
    execute "args `grep -sl '" . @/ . "' $(svn --recursive list)`; echo -n ''"
    argdo execute "substitute//" . a:replacement . "/gc"
endfunction

command -nargs=1 -complete=file Gsubstitute call SvnProjectSubstitute(<f-args>)

Notes:

  • As I have formulated it here, it only searches those files which Subversion has under version control from the current directory recursing down. You can off course generalize that or fit it to a different version management system according to your needs.
  • The pattern which is used is the last pattern that was searched for. Once again, if you do not like this, you can fit it to your needs.
  • Gsubstitute is for "global substitute". You can call the command whatever you want but I like this because :Gs seems to fit stylistically into Vim.
  • This is a nice solution if you have a very large number of files, because it takes the onus off of Vim for searching through them all (i.e. it would probably solve this guy's problem too).
  • The do-nothing echo at the end and the -s passed to grep are to cover up an errors that grep encounters. grep gives a non-zero return code even if it does find good results.

Example use:

/OldClassName
:Gs NewClassName

One reason I like using the previous search pattern is it allows you to quickly give a class an entirely different name by placing your cursor on the class, doing a * or a #, and then your :Gs command, which saves you from having to type out the whole search string.


you can try this tool for find and replace in intercative mode inner a shell

https://sourceforge.net/projects/isartool/

No X server is required, and if you want it's recursive in directory using the option -r.

Ciao


I couldn't get Mario's answer to work, but if I use xargs it works.

Also, most times that I want to do search and replace in multiple files, I want to change an alphanumeric ID from one thing to another, such as renaming a variable or a class, etc. For that, it's very helpful to add a negative lookbehind and negative lookahead to disallow word characters before or after what you're replacing, and for that grep needs the P flag to use Perl regex:

vim `find . -name '*.cpp' -o -name '*.hpp' | xargs grep -Ple '(?<!\w)FIND(?!\w)'`

:bufdo %s/FIND/REPLACE/gce

Of course, it's helpful to wrap this up in a script so that you don't have to re-type it every time. Here's a start:

#!/bin/bash

if [[ $# < 2 ]]; then
    echo "Usage: search-replace <search> <replace>"
    exit 1
fi

search="$1"
replace="$2"

files=`find . -name '*.cpp' -o -name '*.hpp' | xargs grep -Ple "(?<!\w)$search(?!\w)"`

if [[ -z "$files" ]]; then
    echo "No matching .cpp or .hpp files."
    exit 1
fi  

# The stuff surrounding $search is the negative lookahead/lookbehind
vim -c "set hidden | bufdo %s/\(\w\)\@<!$search\(\w\)\@!/$replace/gce" $files

Something that's still not working gracefully for me with Mario's solution is that at the end of the first file it says:

Error detected while processing command line:
E37: No write since last change (add ! to override)

This prevents the search and replace from flowing to the next file. It appears that "set hidden" fixes this, and so I've used that in my above script.

One remaining issue is that after doing search and replace on the first file, it seems to process that first file a second time, undoing your initial replaces. If anyone knows why that is happening, let me know.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜