交互式 shell 中的搜索和替换

12

在我的编辑器中,对多个文件进行搜索和替换很困难。可以使用许多技巧,包括使用 findxargssed/awk 在多个文件中进行搜索和替换,但我无法找到一种交互式的方法。你知道有什么方法吗?


2
什么是交互式的?来自编辑器? - Noam
你是指从命令行进行交互吗? - Straff
8个回答

11

KISS原则:

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

%s后面的第一个字符被用作分隔符。


1
快速提示:如果您想列出git存储库中所有索引文件,请使用git ls-files而不是ls - twink_ml

3
我会添加以下修改到 Dillon 的答案中:
应该在 grep 命令中添加 -le 选项。
vim `find . -name '*.c' -exec grep -le '\<junk\>'  {} \;`

如果你已经在Vim中,但没有选择要替换的内容的机会,请在末尾添加c选项以进行交互式替换,并在开头添加bufdo以遍历每个文件:

:bufdo %s/junk/rubbish/gce

稍后您保存所有的工作:

:bufdo wq!

2
从jhvaras的回答中,我制作了这个bash命令,可以快速搜索和替换(可添加到.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)
}

使用方法如下:

最初的回答:

~$ replace some_text some_new_text

它使用ag进行高级搜索,因为这可能比让vim完成工作更快,但你可以用任何你喜欢的东西代替它。它还以最大速度调用没有插件的vim,在完成所有替换后自动退出并返回到shell。"最初的回答"

1

只需使用 Vim。

首先使用 find 命令列出所有需要更改的文件列表,如下所示。

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

这将使用所有包含字符串junk.c文件启动vim。现在,在vim中对第一个文件进行更改:

:%s/junk/rubbish/g

然后输入:wEnter:nEnter以进行下一个文件。

现在需要重复编辑过程。如果只有一个替换命令和几个文件,则输入:UpEnter以重复替换命令。

但如果有很多,您应该使用map创建一对按键宏。第一个宏将执行替换命令,第二个宏将执行wn命令以保存更改并获取下一个文件。

我已经使用vi(及其旧版本)使用了30年。特别是当您将此两部分编码为两个按键宏时,可以非常快速地运行它,因为很容易按住控制键,并连续键入几个字母,例如RY。这比编写完全自动化的脚本所需的时间更短,而且由于这很容易复制,因此您可以在任何正在使用的计算机上执行它。


1

使用grep、xargs和vi/vim的UNIX方法:

  1. 创建一个别名

当您想要从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 $*'

执行搜索并检查文件列表。
命令:
ge -l 'foo' .
  1. 再次执行搜索,告诉vim在每个文件上进行交互式替换

命令:

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

命令说明:

  • 管道符前部分是重复的搜索命令

  • 在管道符后,我们调用xargs来启动vim编辑每个文件

  • "-L 1"告诉xargs将命令在每行上启动,而不是将所有行合并为单个命令。如果您希望一次启动vim编辑所有文件,请从xargs中删除“-L 1”。

  • "-o"告诉xargs使用实际的终端,而不是/dev/null

对于此命令,您有两个选项:

a) 一次性在所有文件上启动vim编辑器

优点:

  • 命令易于取消:只需退出vim即可

缺点:

  • 您必须依次切换到每个buffer并重新运行"%s"命令

  • 如果您有很多文件,这可能会使vim消耗大量内存(有时甚至像一个全功能IDE!)

b) 逐个文件启动vim编辑器

优点:

  • 一切都是自动化的。Vim被启动并执行搜索命令

  • 内存占用小

缺点:

  • 很难取消:退出vim只会让xargs打开下一个vim

我更喜欢b)变体,因为它是自动化的,并且更符合UNIX风格。通过仔细的规划 - 首先检查文件列表,可能在子目录上运行命令以减少范围 - 它非常好用。

一般优点:

  • 可以使用标准的UNIX工具,在几乎所有平台上都可用

  • 使用起来很好

一般缺点:

  • 您必须两次键入搜索表达式

  • 在复杂的搜索/替换表达式上可能会遇到bash转义问题


0

我无法让Mario的答案起作用,但如果我使用xargs它就可以了。

此外,大多数情况下,当我想在多个文件中进行搜索和替换时,我想将一个字母数字ID从一种东西更改为另一种,例如重命名变量或类等。 为此,添加负回顾和负预测非常有帮助,以禁止在您要替换的内容之前或之后使用单词字符,并且grep需要P标志来使用Perl正则表达式:

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

:bufdo %s/FIND/REPLACE/gce

当然,把这个包裹在脚本中是很有帮助的,这样你就不用每次重新输入了。下面是一个开始:
#!/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

对于Mario的解决方案,仍然没有完美地运行的是,在第一个文件的结尾处它说:

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

这可以防止搜索和替换流到下一个文件。看起来“set hidden”可以解决这个问题,所以我在上面的脚本中使用了它。

剩下的一个问题是,在对第一个文件进行搜索和替换之后,它似乎会再次处理该文件,撤消您最初的替换。如果有人知道为什么会发生这种情况,请告诉我。


0

0

如果你经常需要这样做,或者只想搜索版本控制下的文件,这里是对@jhvaras答案的改进。

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>)

注意:

  • 按照我在这里制定的方式,它只搜索当前目录向下递归的Subversion版本控制下的文件。您可以根据需要将其推广或适应不同的版本管理系统。
  • 使用的模式是上次搜索的模式。再次强调,如果您不喜欢这个,可以根据自己的需求进行调整。
  • Gsubstitute代表“全局替换”。您可以随意命名该命令,但我喜欢这个名称,因为:Gs在Vim中似乎很符合风格。
  • 如果您有大量文件,则这是一个不错的解决方案,因为它可以减轻Vim搜索所有文件的负担(即它可能也可以解决this guy's problem)。
  • 最后的无操作回显和传递给grep-s是为了掩盖grep遇到的任何错误。即使grep找到了好的结果,它也会返回非零的返回代码。

示例用法:

/OldClassName
:Gs NewClassName

我喜欢使用之前的搜索模式的一个原因是它允许您通过将光标放在类上,执行*#,然后执行:Gs命令来快速为类命名,这样可以避免您输入整个搜索字符串。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接