虽然现在有很多图形界面的diff工具,但对于有命令行情节的人来说,当飞快的在terminal下敲击键盘时,总不希望再拿鼠标去点击其它地方,况且有时候图形界面占用资源多,我的MBA就经常启动diffmerge时卡住,但vimdiff又只能在一个标签里比较一组文件的diff,如果想比较两个目录下文件的diff,它就显得无能为力了
假设我们要实现一个工具叫diffdir,先让我们脑洞打开设想一下它应该是怎样的
- 我希望能列出两个目录下文件名相同但内容不同的所有文件,并进行编号
- 我希望通过选择编号,打开需要比较diff的文件
- 如果想比较多组文件的diff,我希望每个vim标签打开一组文件比较
- 最好能过滤掉非文本文件,因为我不希望用vim打开一对二进制乱码
- 最好还能有交互,我可以选择只查看我感兴趣的文件,而不是一次打开所有文件的diff,当退出vim时我还可以继续选择
假设有两个目录分别是A和B,目录结构如下
A
├── file1
├── file2
└── file3
B和A目录结构以及对应文件名都相同,其中file1和file2的内容不同,file3内容相同,那么当我们运行diffdir A B
时,它应该是这样的界面
当我们选择编号1时,vim会打开一个标签对比两个目录下file1的差异
当我们选择1,2
或1-2
时,vim会打开两个标签分别比较file1和file2的差异
由于这个例子有diff的文件数量较少,我们还可以选择a一次打开所有文件的diff
如果diff文件个数较多,我们可以分批打开,并且当我们退出vim后还可以继续选择
接下来是实现
vim比较文件diff¶
我们都知道vimdiff的用法,其实vimdiff A/file1 B/file1
等价于vim -d A/file1 B/file2
,又或者更原始一点,我们可以分两步来比较两个文件的diff
- 执行
vim A/file1
- 在normal模式下输入
:vertical diffsplit B/file1
虽然人们不会用这么麻烦的命令去比价文件的diff,但往往最基本的命令反而能组合出更多的功能,就像搭积木一样,我们只需要几个基本的形状,就可以通过自己的想象搭建多彩的世界,而vim的这些基本命令就像积木一样,我们要做的是利于好这些积木
vim在新标签比较文件diff¶
假设我们已经用上面的命令打开了vim并比较file1的diff,如果我们希望新建一个标签来比较file2的diff呢,还是要用到基本的ex命令
- 在normal模式下执行
:tabnew A/file2
- 在normal模式下执行
:vertical diffsplit B/file1
vim批量执行命令¶
以上两个示例就是我们需要的积木,有了积木,我们就可以组合出强大的命令,现在要做的是同时打开两组文件的diff,并且每个标签一组diff
通过查看vim帮助我们发现vim有如下两个参数
-c <command> 加载第一个文件后执行 <command>
-S <session> 加载第一个文件后执行文件 <session>
这两个参数都可以让vim启动时执行一些命令,其中-c是从参数读取命令,-S是从文件读取命令,于是我们就可以将需要执行的命令存入文件,启动vim时通过-S参数加载该文件,就能达到我们批量执行命令的目的。假设我们需要打开两个标签,分别比较A,B目录下file1和file2的diff,事先创建vim.script如下(文件名随意,最好采用绝对路径,以免受到vim配置里autochdir的影响)
edit A/file1
vertical diffsplit B/file1
tabnew A/file2
vertical diffsplit B/file2
然后执行vim -S vim.script
,看看是否如你所愿,打开了两个标签,分别比较file1和file2的diff。注意,为了
最终实现¶
既然有了这些积木,那我们就可以灵活的根据需要编写脚本实现我们的需求,下面是我最终的实现,也可以在github上查看源码
https://github.com/handy1989/vim/blob/master/diffdir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | #!/bin/bash
if [ $# -ne 2 ];then
echo "Usage:$0 dir1 dir2"
exit 1
fi
if [ ! -d $1 -o ! -d $2 ];then
echo "$1 or $2 is not derectory!"
exit 1
fi
## 注意,Mac的readlink程序和GNU readlink功能不同,Mac需要下载greadlink
arg1=`greadlink -f $1`
arg2=`greadlink -f $2`
tmp_dir=/tmp/tmp.$$
rm -rf $tmp_dir
mkdir -p $tmp_dir || exit 0
#echo $tmp_dir
trap "rm -rf $tmp_dir; exit 0" SIGINT SIGTERM
## 注意,Mac和Linux的MD5程序不同,请根据需求使用,这里是Mac版的用法
function get_file_md5
{
if [ $# -ne 1 ];then
echo "get_file_md5 arg num error!"
return 1
fi
local file=$1
md5 $file | awk -F"=" '{print $2}'
}
function myexit
{
rm -rf $tmp_dir
exit 0
}
function show_diff
{
if [ $# -ne 1 ];then
return 1
fi
local diff_file=$1
echo "diff file:"
printf " %-55s %-52s\n" $arg1 $arg2
if [ -f $tmp_dir/A_ony_file ];then
awk '{printf(" [%2d] %-50s\n", NR, $1)}' $tmp_dir/A_ony_file
python -c 'print "-"*100'
fi
awk '{printf(" [%2d] %-50s %-50s\n", NR, $1, $1)}' $diff_file
echo "(s):show diff files (a):open all diff files (q):exit"
echo
}
function check_value
{
local diff_file=$1
local value=$2
tmp_file=$tmp_dir/tmp_file
>$tmp_file
for numbers in `echo "$value" | tr ',' ' '`
do
nf=`echo "$numbers" | awk -F"-" '{print NF}'`
if [ $nf -ne 1 -a $nf -ne 2 ];then
return 1
fi
begin=`echo "$numbers" | awk -F"-" '{print $1}'`
end=`echo "$numbers" | awk -F"-" '{print $2}'`
if [ -z "$end" ];then
sed -n $begin'p' $diff_file >> $tmp_file
else
if [ "$end" -lt $begin ];then
return 1
fi
sed -n $begin','$end'p' $diff_file >> $tmp_file
fi
if [ $? -ne 0 ];then
return 1
fi
done
awk -v dir1=$arg1 -v dir2=$arg2 '{
if (NR==1)
{
printf("edit %s/%s\nvertical diffsplit %s/%s\n", dir1, $0, dir2, $0)
}
else
{
printf("tabnew %s/%s\nvertical diffsplit %s/%s\n", dir1, $0, dir2, $0)
}
}' $tmp_file
}
#############################################################
# 获取diff info
#############################################################
for file in `find $arg1 | grep -v "/\." | grep -v "^\."`
do
file_relative_name=${file#$arg1/}
file $file | grep -Eq "text"
if [ $? -ne 0 ];then
continue
fi
if [ -f $arg2/$file_relative_name ];then
file $arg2/$file_relative_name | grep -Eq "text"
if [ $? -ne 0 ];then
continue
fi
md5_1=`get_file_md5 $file`
md5_2=`get_file_md5 $arg2/$file_relative_name`
if [[ "$md5_1" = "$md5_2" ]];then
continue
fi
## file not same
echo "$file_relative_name" >> $tmp_dir/diff_file
else
echo "$file_relative_name" >> $tmp_dir/A_ony_file
fi
done
#############################################################
# 根据输入标签打开用vim打开文件比较diff
#############################################################
if [ ! -f $tmp_dir/diff_file ];then
exit
fi
show_diff $tmp_dir/diff_file
while true
do
echo -n "Please choose file number list (like this:1,3-4,5):"
read value
if [[ "$value" = "s" ]] || [[ "$value" = "S" ]];then
show_diff $tmp_dir/diff_file
continue
elif [[ "$value" = "q" ]] || [[ "$value" = "Q" ]];then
myexit
elif [[ "$value" = "a" ]] || [ "$value" = "A" ];then
value="1-$"
fi
vim_script=`check_value $tmp_dir/diff_file "$value" 2>/dev/null`
if [ $? -ne 0 ];then
echo "invalid parameter[$value]!"
else
vim -c "$vim_script"
fi
done
|