Find & Replace

sed可以很方便的对多个源文件里进行find & replace操作,但是如果每个replace的地方都需要人工确认一下的话, 那么就只能使用vim了.

首先,要找出所有包含某个匹配串的源文件, 这里可以用grep或者ack. 以ack为例

ack "$pattern1" -l --php
  • pattern1 就是要查找并替换掉的字符串的正则表达式
  • -l 参数表示只列出文件名而不输出匹配的行内容
  • --php 表示只查找后缀为php的文件

接下来, 就是对每个文件做查找和替换,并在每一处需要替换的地方进行人工确认.

vim -c "%s/$pattern1/$pattern2/gc | wq" $filename
  • -c 参数表示vim打开文件后自动执行的命令, 上面的命令就是打开文件, 查找并确认替换, 最后保存和关闭文件

最后, 把这两条命令结合起来就搞定了. 因为有多个文件, 所以使用xargs来进行参数传递

ack "$pattern1" -l --php | xargs -L 1 vim -c "%s/$pattern1/$pattern2/gc | wq"
  • xargs -L 1 表示把输入的每一行做为一个参数传递给后面的命令

大功告成? 执行这条命令看看, 你就会发现, 每打开一个文件, 都会出现一个警告:

Vim: Warning: input is not from a terminal

而且,在替换完所有的文件退回终端之后, 你输入任何字面都不会在终端上显示. 只好关闭之后重新打开一个终端.

显然, 上面的命令不是解决问题的好办法.

在google之后, 找到了解决办法. 出现问题的原因就是xargs启动命令时, 没有把用户终端作为标准输入. 解决办法就是在xargs的命令中新启动一个shell,并把标准输入指定为当前的用户终端.

ack "$pattern1" -l --php | xargs -L 1 bash -c "</dev/tty vim -c '%s/$pattern1/$pattern2/gc | wq' $0"
  • bash的 -c 参数和 vim 的 -c 参数含义是一样的,表示启动后自动执行命令
  • </dev/tty 的含义就是把 /dev/tty 作为标准输入
  • $0 则表示 bash -c "command" 后面的参数, 注意这里的$需要转义, $0 的含义具体可见 bash的 man page中关于 -c 命令的说明

    -c string If the -c option is present, then commands are read from
              string. If there are arguments after the string, they are
              assigned to the positional parameters, starting with $0.

这是第一种解决办法, 已经可以完全满足find & replace的需求.所以写一个脚本 find_and_replace.sh

#!/bin/bash
# find_and_replace.sh

function print_usage(){
    echo " $0 <find pattern> <replace string>"
    exit
}

if test $# -ne 2; then
    print_usage
fi

pattern1=$1
pattern2=$2
ack "$pattern1" -l --php | xargs -L 1 bash -c "</dev/tty vim -c '%s/$pattern1/$pattern2/gc | wq' $0"

执行命令

./find_and_replace.sh "abc" "def"

就可以递归查找当前目录下所有包含abc串的php文件, 在每一处匹配的地方进行确认是否把abc替换为def.

这个事情还有另外一个办法: 抛弃xargs, 直接把ack的输出作为参数传递给vim

vim -c "%s/$pattern1/$pattern2/gc | wq" $(ack "$pattern1" -l --php)

可是当你执行这条命令的时候, 你就会发现在替换完第一个文件,执行 wq命令时会报错

Error detected while processing command line:
E173: 1 more file to edit

这是因为vim只对第一个打开的文件执行了替换操作, 然后就立刻执行 wq 操作了. 后面的文件并没有处理. 解决办法就是使用argdo

vim -c "argdo %s/$pattern1/$pattern2/gc | w" -c "q" $(ack "$pattern1" -l --php)

这样, vim会先在所有的文件中执行替换和保存操作, 然后退出.
于是,有了 find_and_replace.sh 的另外一个版本:

#!/bin/bash
# find_and_replace.sh

function print_usage(){
    echo " $0 <find pattern> <replace string>"
    exit
}

if test $# -ne 2; then
    print_usage
fi

pattern1=$1
pattern2=$2
vim -c "argdo %s/$pattern1/$pattern2/gc | w" -c "q" $(ack "$pattern1" -l --php)

Posted via UltraBlog.vim.

Comments