生產環境中,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機器上的一樣,同時判斷該文件是否存在