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.
Last modified on 2011-07-31