利用Vim进行文件夹对比的三种方式

前言

最近经常使用vim, 心血来潮想研究了一下如何用Vim进行代码merge. 在Windows下有Beyond Compare和WinMerge等软件,可以比较两个目录结构及文件内容的异同,并以图形界面的形式呈现给用户。Vim有的vimdiff可以进行文件内容的对比和merge操作,但是很遗憾的是只针对单个文件比较,文件夹的比较就无能为力了。

在网上这方面的介绍不多,只搜到https://blog.****.net/littlewhite1989/article/details/45312081。该文章介绍了一种linux脚本结合Vim进行文件对比的方法,但是原脚本有些地方没有考虑周全,所以对其进行了一些优化。比如文件名或者文件夹名里面有空格等特殊符的情况有异常,只在文件夹B里面的文件或者目录漏掉了。

在区分两个文件是否相同时,原文是用md5sum命令实现,在子文件较多时处理时间比较长。这部分可以用linux自带的diff命令进行区分,据此进行了脚本优化。

另外,在Vim的官网上(https://www.vim.org/sponsor/index.php)搜索到了一款比较文件夹的件:https://github.com/will133/vim-dirdiff,可以直接对文件夹进行merge,也可以进vimdiff模式对每个差别进行操作,功能更强大。

基于md5sum的文件夹对比

基本思想参考https://blog.****.net/littlewhite1989/article/details/45312081,以下脚本是对原方法的优化。

#!/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=`readlink -f "$1"`
arg2=`readlink -f "$2"`


tmp_dir=$PREFIX/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
    md5sum "$file" | awk -F" " '{print $1}'
}

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_only_file ];then
        awk '{printf("    [%2d] %-50s\n", NR, $0)}' $tmp_dir/A_only_file
	python -c 'print("-"*100)'
    fi
    if [ -f $tmp_dir/B_only_file ];then
	    awk '{for(i=0;i<60;i++)printf(" "); printf(" [%2d] %-50s\n",NR, $0)}' $tmp_dir/B_only_file
	python -c 'print("-"*100)'
    fi
	if [ -f $diff_file ];then
    	awk '{printf("    [%2d] %-50s  %-50s\n", NR, $0, $0)}' $diff_file
    	echo "(s):show diff files (a):open all diff files (q):exit"
    	echo
	fi
}

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 file1 in `find "$arg1" | sed 's% %#%g'`
do
    file=`echo $file1 | sed 's%#% %g'`
    file_relative_name=${file#$arg1/}
    file "$file" | grep -Eqv "directory"
    if [ $? -ne 0 ];then
        continue
    fi
    if [ -f "$arg2/$file_relative_name" ];then
        file "$arg2/$file_relative_name" | grep -Eqv "directory"
        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_only_file
    fi
done

for file1 in `find "$arg2" | sed 's% %#%g'`
do
    file=`echo $file1 | sed 's%#% %g'`
    file_relative_name=${file#$arg2/}
    file "$file" | grep -Eqv "directory"
    if [ $? -ne 0 ];then
        continue
    fi
    if [ ! -f "$arg1/$file_relative_name" ];then
        echo "$file_relative_name" >> $tmp_dir/B_only_file
    fi
done

#############################################################
# 根据输入标签打开用vim打开文件比较diff
#############################################################
if [[ ! -f $tmp_dir/diff_file && ! -f $tmp_dir/A_only_file && ! -f $tmp_dir/B_only_file ]];then
    echo folders are the same!
    myexit
fi

show_diff $tmp_dir/diff_file
while true
do
	if [ -f $tmp_dir/diff_file ];then
    	echo -n "Please choose file number list (like this:1,3-4,5):"
	else
		echo "No diff files,enter 'q' to exit!"
		echo -n ":"
	fi
    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

基于diff的文件夹对比

#!/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=`readlink -f "$1"`
arg2=`readlink -f "$2"`


tmp_dir=$PREFIX/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
    md5sum $file | awk -F" " '{print $1}'
}

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_only_file ];then
        awk '{printf("    [%2d] %-50s\n", NR, $0)}' $tmp_dir/A_only_file
	python -c 'print("-"*100)'
    fi
    if [ -f $tmp_dir/B_only_file ];then
	    awk '{for(i=0;i<60;i++)printf(" "); printf(" [%2d] %-50s\n",NR, $0)}' $tmp_dir/B_only_file
	python -c 'print("-"*100)'
    fi
	if [ -f $diff_file ];then
    	awk '{printf("    [%2d] %-50s  %-50s\n", NR, $0, $0)}' $diff_file
    	echo "(s):show diff files (a):open all diff files (q):exit"
    	echo
	fi
}

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
#############################################################
temp_path1=$1
temp_path2=$2

#如果路径最后面没有带"/",都加上"/"
if [ ${1:0-1} != "/" ]; then
	temp_path1=`echo $1/`
fi
if [ ${2:0-1} != "/" ]; then
	temp_path2=`echo $2/`
fi

#调用diff命令,结果输出到diff_out
diff -rq "$temp_path1" "$temp_path2"  > $tmp_dir/diff_out

#处理两个文件有不同的文件情况
grep "^Files" $tmp_dir/diff_out | sed "s%$temp_path2%#&%" | cut -d"#" -f2 | sed "s%$temp_path2/*%%" > $tmp_dir/diff_file
grep "Binary files" $tmp_dir/diff_out | sed "s%.*$1\/*\(.*\) and .*%\1%" >> $tmp_dir/diff_file
sed -i 's/ differ$//' $tmp_dir/diff_file
#判断文件是否为空
#if [ ! -s $tmp_dir/diff_file ];then
#    rm -rf $tmp_dir/diff_file
#fi


#handle left folder only files
grep  "Only in $temp_path1" $tmp_dir/diff_out |cut -d" " -f3- | sed 's%: %/%' > $tmp_dir/A_only_file
sed -i "s#.*$temp_path1/*##" $tmp_dir/A_only_file
grep -w "File ${temp_path1%/*}" $tmp_dir/diff_out | sed "s%\(File ${temp_path1%/*}.*\)while.*%\1%" >> $tmp_dir/A_only_file

#handle right folder only files
grep  "Only in $temp_path2" $tmp_dir/diff_out |cut -d" " -f3- | sed 's%: %/%' > $tmp_dir/B_only_file
sed -i "s#.*$temp_path2/*##" $tmp_dir/B_only_file
grep -w "File ${temp_path1%/*}" $tmp_dir/diff_out |sed "s%.*while \(.*${temp_path2%/*}.*\)%\1%" >> $tmp_dir/B_only_file

#############################################################
# 根据输入标签打开用vim打开文件比较diff
#############################################################
if [[ ! -s $tmp_dir/diff_file && ! -s $tmp_dir/A_only_file && ! -s $tmp_dir/B_only_file ]];then
	echo folders are the same!
	myexit
fi

show_diff $tmp_dir/diff_file
while true
do
	if [ -s $tmp_dir/diff_file ];then
    	echo -n "Please choose file number list (like this:1,3-4,5):"
	else
		echo "No diff files,enter 'q' to exit!"
		echo -n ":"
	fi
    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

基于vim插件的方式

参考https://www.vim.org/scripts/script.php?script_id=102
利用Vim进行文件夹对比的三种方式