案例六、检测两台机器上文件的差异

生产环境中,web服务器大多会做负载均衡,所以有多台机器上跑着同样的web程序代码。如果严格按照规范流程上线,即不人为特意更改代码,那么这多台机器上的代码一定是一样的,并不会存在两台机器上同一个文件内容不同的现象。但是,本案例的需求就是要检查两台机器上同一个文件的差异,毕竟我们不能确定服务器上的代码是否有人为改动过。

需求如下:

1)两台机器A和B,检查的目标目录为/data/wwwroot/www.abc.com/,路径一致。

2)需要过滤目录uploads、tmp两个目录,即这两个目录下的文件不需要进行对比。

3)以A机器上的文件作为标准,B机器少了文件和改了文件需要记录,多了文件不用考虑。

4)假设A机器可以免密登录B机器

5)把有差异和丢失的文件列表存入文件/data/change.log


知识点一:A机器免密登录B机器

这里所谓的免密登录指的是,A机器可以直接ssh B机器,不用输入密码。

实现免密登录的方式有两种:一个是使用expect脚本,另外一种是通过密钥认证,具体操作步骤如下。


首先在A机器上生成一对密钥,其中包含公钥和私钥,私钥放在A机器上,公钥放在B机器上,私钥用来加密,公钥用来解密。生成密钥对的命令如下:

# ssh-keygen     //执行后一直按回车即可
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):   //可以在这里更改密钥保存的路径,如果按回车则存放到默认路径下。
Enter passphrase (empty for no passphrase):   //这里需要输入密钥的密码,按回车则密钥密码为空。
Enter same passphrase again:   //密码确认。
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.

说明:

因为当前用户是root,所以会在root家目录/root/下生成一个.ssh目录,密钥对在.ssh目录下。


这样在~/.ssh/目录下就会生成两个文件id_rsa和id_rsa.pub,其中id_rsa为私钥,id_rsa_pub为公钥。如果机器上已经存在了这样的一对文件,就可以直接使用它们。

也可以指定文件的名字,如下:

# ssh-keygen -f /tmp/123     //这样会在/tmp/下生成123和123.pub两个文件

生成密钥对后,需要把公钥传到目标机器上,假设B机器的IP为192.168.0.110,则传输公钥的方法为:

# ssh-copy-id [email protected]   //这里的root@可以省略掉,只写IP。这个操作,需要我们手动输入对方机器目标用户(root)的密码。

上面这个命令会把~/.ssh/id_rsa.pub文件内容写到目标机器目标用户(如root)家目录下的.ssh/authorized_keys文件里,注意这里是追加写入。

若想指定特定的私钥,比如,上例中那个/tmp/123,命令是:

# ssh-copy-id -i /tmp/123 [email protected]  //这里的-i选项后指定私钥文件路径,它可以自动产生公钥。

做完上面的两步,就可以用ssh命令登录目标机器了,而且不用输入密码。它默认会找~/.ssh/id_rsa这个私钥,但如果使用的另外的私钥文件,还需要在ssh时指定文件路径,如下:

# ssh -i /tmp/123 192.168.0.110


知识点二:find中过滤指定目录

先看一个简单的需求,要求查找当前目录下除了123目录(./123)外,所有的文件,有两种方法。

方法一:

# find ./ -type f |grep -v '^\./123'

说明:这里首先find出所有文件列表,然后再针对结果用grep排除掉123目录。对于此方法,简单的场景是没问题的,如果某个文件的文件名中包含了123关键词,就不好处理了。

方法二:

# find ./ -path './123*' -prune -o -type f

说明:-path类似于shell中的正则匹配,指定字符串作为寻找目录的范本样式。-prune,不寻找字符串作为寻找文件或目录的范本样式。-prune -o组合在一起使用,会把-prune前面的匹配排除掉,如果是要排除多个目录,可以这么用:

# find ./ \( -path './123*' -o -path './abc*' \) -prune -o -type f

注意:-path后面跟的那个目录名字后面不要加/,否则就不正确。-o是-or的意思。

# ls
111  123  12333  222  233  333  abc
# ls 123
ioio  lsls
# ls 233/
ai
# ls 333/
ia
# ls abc/
ei
# find ./ \( -path './123*' -o -path './abc*' \) -prune -o -type f
./123
./233/ai
./333/ia
./111
./222
./12333
./abc

说明:当前目录下,123目录和abc目录下的文件没有被列出来,其他文件都列出来了,说明123和abc目录下的文件已经被过滤排除。


知识点三:对比两个文件是否有差异

可以使用diff命令,也可以对比两个文件的md5sum值。diff命令能比较出差异来,并且会把具体差异的内容显示出来,在本例中并不需要知道具体差异的内容,而且我们没有办法用diff命令对比本机和远程机器上的两个文件,所以只能使用md5sum。先看一下diff命令的用法:

# echo -e '123\n234' > 2.txt
# echo -e '123\nabc' > 1.txt
# cat 1.txt 
123
abc
# cat 2.txt 
123
234
# diff 1.txt 2.txt 
2c2
< abc
---
> 234

说明:echo -e可以识别换行\n,diff出来的结果,2c2指的是第二行有差异,<表示左边的文件,>表示右边的文件。

而md5sum命令这样使用:

# md5sum 2.txt
0941216c25fdb3bcf7876f97bb79c865  2.txt

前面这个字符串,就是2.txt文件的md5值,如果修改一下2.txt,则md5值会发生改变:

# echo '123' >> 2.txt 
# md5sum 2.txt 
48ac7d30e9f2c450f2b9d9198577d0e2  2.txt

要想判断两个文件是不是一样,只要对比两个文件的md5值即可。


知识点四:嵌入文档(Here Document)

嵌入文档,英文名叫Here Document,大家可能见过,但恐怕并不知道这个专业术语,先看一段shell代码:

#!/bin/bash
cat > 1.txt <<EOF
Hello
My
name
is aming.
This is a test text.
EOF

运行这个脚本后,会产生1.txt文档,内容为:

Hello
My
name
is aming.
This is a test text.

这个用法就是嵌入文档。这里的EOF叫做标识符,还可以换成其他字符串,比如写成ABC、123等等,随便自定义,但要保证前后对应,也不要造成和其他字符串混淆。通常大家习惯写EOF,这样也容易让别人识别。就跟shell脚本名字以.sh结尾同样的道理。


嵌入文档需要注意一点,最后面那个标识符EOF必须要顶格写,不一定非得cat << EOF,也可以换成其他命令,比如:

#!/bin/bash
wc -l <<EOF
1
2
3
EOF


知识点五:while循环遍历文件

在shell中,用for循环遍历一个文件内容时,也就是说按行来遍历时会有一些问题。因为for循环遍历对象以空白字符或者换行作为分隔符,例如下面文本用for循环则无法实现预期:

# vim test.txt
ab 1
123
234
# for s in `cat test.txt`;do echo $s;done
ab
1
123
234

而使用while循环,效果是这样的:

# cat test.txt |while read s ;do echo $s;done
ab 1
123
234

所以,要想遍历一个文件的所有行时,可以用while read循环来实现。


本案例参考脚本

结合以上五个知识点,得出最终参考脚本,内容如下:

#!/bin/bash
##对比两台机器上的文件差异
##作者:
##日期:
##版本:v0.2

#假设B机器IP为192.168.0.110
B_ip=192.168.0.110
dir=/data/wwwroot/www.abc.com
#首先检查/tmp/md5.list文件是否存在,若存在就删掉,避免影响后续操作
[ -f /tmp/md5.list ] && rm -f /tmp/md5.list

#把除了uploads以及tmp目录外的其他文件全部列出来
cd $dir
find . \( -path "./uploads*" -o -path "./tmp*" \) -prune -o -type f > /tmp/file.list

#用while循环,求出所有文件的md5值,并写入一个文件里
cat /tmp/file.list|while read line
do
  md5sum $line
done > /tmp/md5.list

#将md5.list拷贝到B机器
scp /tmp/md5.list $B_ip:/tmp/

#判断/tmp/check_md5.sh文件是否存在
[ -f /tmp/check_md5.sh ] && rm -f /tmp/check_md5.sh

#用Here Document编写check_md5.sh脚本内容
cat >/tmp/check_md5.sh << EOF
#!/bin/bash
dir=/data/wwwroot/www.abc.com
##注意,这里涉及到的特殊字符都需要脱义,比如反引号和$
cd \$dir
n=\`wc -l /tmp/md5.list|awk '{print \$1}'\`
for i in \`seq 1 \$n\`
do
  file_name=\`sed -n "\$i"p /tmp/md5.list |awk '{print \$2}'\`
  md5=\`sed -n "\$i"p /tmp/md5.list |awk '{print \$1}'\`
  if [ -f \$file_name ]
  then
      md5_b=\`md5sum \$file_name|awk '{print \$1}'\`
  if [ \$md5_b != \$md5 ]
  then
     echo "\$file_name changed."
  fi
  else
     echo "\$file_name lose."
  fi
done > /data/change.log
EOF
scp /tmp/check_md5.sh $B_ip:/tmp/
ssh $B_ip "/bin/bash /tmp/check_md5.sh"

说明:

此脚本的思路是:

1)在A机器上遍历所有文件,对每一个文件计算md5值存入一个临时文件里

2)把临时文件拷贝到B机器,留着备用

3)编写在B机器上要执行的对比md5值的脚本

4)将脚本传到B机器

5)执行脚本


而在B机器上执行的脚本,思路是:

1)根据md5值临时文件里的文件列表,逐一计算B机器上文件的md5值

2)对比B机器上文件的md5值是否和A机器上的一样,同时判断该文件是否存在

猜你喜欢

转载自blog.51cto.com/13576245/2422917