標籤:工具 vim
雖然現在有很多圖形介面的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/file1vertical diffsplit B/file1tabnew A/file2vertical diffsplit B/file2
然後執行vim -S vim.script
,看看是否如你所願,開啟了兩個標籤,分別比較file1和file2的diff。注意,為了
最終實現
既然有了這些積木,那我們就可以靈活的根據需要編寫指令碼實現我們的需求,下面是我最終的實現,也可以在github上查看源碼
https://github.com/handy1989/vim/blob/master/diffdir
#!/bin/bashif [ $# -ne 2 ];then echo "Usage:$0 dir1 dir2" exit 1fiif [ ! -d $1 -o ! -d $2 ];then echo "$1 or $2 is not derectory!" exit 1fi## 注意,Mac的readlink程式和GNU readlink功能不同,Mac需要下載greadlinkarg1=`greadlink -f $1`arg2=`greadlink -f $2`tmp_dir=/tmp/tmp.$$rm -rf $tmp_dirmkdir -p $tmp_dir || exit 0#echo $tmp_dirtrap "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 fidone############################################################## 根據輸入標籤開啟用vim開啟檔案比較diff#############################################################if [ ! -f $tmp_dir/diff_file ];then exitfishow_diff $tmp_dir/diff_filewhile truedo 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" fidone
vim比較目錄diff