当前位置 博文首页 > goodcitizen:使用 shell 脚本自动对比两个安装目录并生成差异补
公司各个业务线的安装包小则几十兆、大则几百兆,使用自建的升级系统向全国百万级用户下发新版本时,流量耗费相当惊人。有时新版本仅仅改了几个 dll ,总变更量不过几十 K 而已,也要发布一个完整版本。为了降低流量费用,我们推出了补丁升级的方式:产品组将修改的 dll 单独挑选出来,加上一个配置文件压缩成包,上传到自建的升级后台;在客户端,识别到补丁包类型后,手动解压并替换各个 dll 完成安装(之前是直接启动下载好的安装包)。这种方式一经推出,受到了业务线的追捧。然而在使用过程中,也发现一些问题,就是在修改完一个源文件后,受影响的往往不止一个 dll,如果仅把其中一两个 dll 替换了,没替换的 dll 很可能就会和新的 dll 产生接口不兼容,从而引发崩溃。而有的安装包包含了几十个、上百个 dll,如果一一对比,非常费时费力。特别是一些 dll 仅仅是编译时间不一样,通过普通的文件对比工具,根本无法判断这个 dll 的源码有没有改过,这让开发人员非常头大。
其实这个问题用 c++ 写个程序是可以解决的,但是一想到要遍历目录、构造文件名 map、对比两个目录中的文件名、对比相同文件名的内容、复制文件到目标目录、压缩目标目录…这一系列操作时,我觉得还是算了 —— 都得从头开始写,工作量不小。而 msys2 或 windows 中就有不少现成的命令可以用,例如对比目录可以用 diff -r 命令、对比 win32 可执行文件可以用 dumpbin /disasm 命令反编译然后再用 diff 命令对比、压缩文件夹可以使用 7z 命令等等,完全不用重复造轮子,直接用 shell 将它们粘合起来就完事了!下面就来看看我是怎么用 shell 脚本来写这个小工具吧。
这个脚本一开始先处理输入的命令行参数:
1 # return code: 2 # 0 : success 3 # 1 : no difference 4 # 2 : compress failure 5 # 3 : create file/dir failure (privilege ?) 6 # 126 : file/dir existent 7 # 127 : invalid arguments 8 9 function usage () 10 { 11 echo "Usage: diffpacker.sh -o oldversionfolder -n newversionfolder -r relativepath -x exportfolder -v version [-s sp] [-t (verbose)] [-e (exactmode)]" 12 13 exit 127 14 } 15 16 srcdir= 17 dstdir= 18 reldir= 19 outdir= 20 version= 21 sp=0 22 verbose=0 23 exactmode=0 24 setupdir="setup" 25 # pure windows utilities subdir 26 win32="win32" 27 28 if [ "${$*/-t//}" != "$*" ]; then 29 # dump parameters when verbose on 30 echo "total $# param(s):" 31 for var in $*; do 32 echo "$var" 33 done 34 fi 35 36 37 while getopts "o:n:r:x:v:s:te" arg 38 do 39 case $arg in 40 o) 41 srcdir=$OPTARG 42 ;; 43 n) 44 dstdir=$OPTARG 45 ;; 46 r) 47 reldir=$OPTARG 48 ;; 49 x) 50 outdir=$OPTARG 51 ;; 52 v) 53 version=$OPTARG 54 ;; 55 s) 56 sp=$OPTARG 57 ;; 58 t) 59 verbose=1 60 ;; 61 e) 62 exactmode=1 63 ;; 64 ?) 65 echo "unkonw argument: $arg" 66 usage 67 exit 127 68 ;; 69 esac 70 done 71 72 # reldir can be empty 73 if [ -z "$srcdir" -o -z "$dstdir" -o -z "$outdir" -o -z "$version" ]; then 74 echo "empty parameter found: $srcdir, $dstdir, $outdir, $version" 75 usage 76 exit 127 77 fi 78 79 #replace all \ to / to avoid shell string choked on \ 80 srcdir=${srcdir//\\/\/} 81 dstdir=${dstdir//\\/\/} 82 reldir=${reldir//\\/\/} 83 outdir=${outdir//\\/\/} 84 85 echo "srcdir=$srcdir" 86 echo "dstdir=$dstdir" 87 echo "reldir=$reldir" 88 echo "outdir=$outdir" 89 echo "version=$version" 90 echo "sp=$sp" 91 echo "verbose=$verbose" 92 echo "exactmode=$exactmode" 93 echo "" 94 95 if [ ! -d "$srcdir" ]; then 96 echo "not a directory : $srcdir" 97 exit 126 98 fi 99 100 if [ ! -d "$dstdir" ]; then 101 echo "not a directory : $dstdir" 102 exit 126 103 fi 104 105 #if [ -e "$outdir" ]; then 106 resp=$(ls -A "$outdir") 107 if [ "$resp" != "" ]; then 108 echo "out directory not empty: $outdir, fatal error!" 109 exit 126 110 fi 111 112 if [ "${outdir:$((${#outdir}-1))}" == "/" ]; then 113 # remove tailing / 114 outdir=${outdir%?} 115 fi 116 117 if [ ! -z "$reldir" ] && [ "${reldir:$((${#reldir}-1))}" == "/" ]; then 118 # remove tailing / 119 reldir=${reldir%?} 120 fi 121 122 srcasm="src.asm" 123 dstasm="dst.asm" 124 dirdiff="dir.diff" 125 patdiff="diffpattern.txt" 126 itemcnt=0 127 jsonhead=\ 128 "{\n"\ 129 " \"version\": \"$version\",\n"\ 130 " \"sp\": \"$sp\",\n"\ 131 " \"actions\": \n"\ 132 " [\n" 133 134 json= 135 jsontail=\ 136 "\n ]\n"\ 137 "}\n" 138 139 echo "exclude patterns: " 140 while read line 141 do 142 echo $line 143 done < "$patdiff" 144 145 # to avoid user not end file with \n 146 if [ ! -z "$line" ]; then 147 echo "$line" 148 fi 149 echo ""
简单解说一下:
经过前期的铺垫,进入第一个重头戏:
1 if [ -f "$patdiff" ]; then 2 diff -qr "$srcdir" "$dstdir" -X "$patdiff" > "$dirdiff" 3 else 4 diff -qr "$srcdir" "$dstdir" > "$dirdiff" 5 fi 6 7 while read line 8 do 9 if [ $verbose != 0 ]; then 10 echo $line 11 fi 12 13 tmp=$(echo $line | sed -n 's/Files \(.*\) and \(.*\) differ$/\1\\n\2/p')