同步 UNIX 文件
有許多工具可以用來跨 Unix® 目錄同步文件,但是要想有效且安全地完成這個(gè)任務(wù),就需要多做一些工作。本文介紹跨 UNIX 文件系統(tǒng)和不同的計(jì)算機(jī)系統(tǒng)安全地同步文件的解決方案,包括如何為了執(zhí)行備份同步文件的加密版本。
文件同步就是在一個(gè)位置添加、修改或刪除文件時(shí),在另一個(gè)位置添加、修改或刪除同一個(gè)文件的過程。本文討論三個(gè)實(shí)用程序,cp、tar 和 rsync,它們都有助于同步 UNIX 文件。cp 和 tar 命令的同步功能有限,而 rsync 提供很全面的選項(xiàng);盡管如此,它們都有各自適用的場合。
用 cp 命令執(zhí)行直接復(fù)制
盡管 cp 命令并不是真正的同步命令,但它可能是在兩個(gè)位置之間復(fù)制文件的最簡單方法。對于單一文件復(fù)制,cp 顯然是非常高效的:$ cp source destination。
要想復(fù)制整個(gè)目錄結(jié)構(gòu),可以使用 -r 選項(xiàng)遞歸地把整個(gè)目錄結(jié)構(gòu)從一個(gè)位置復(fù)制到另一個(gè)位置:$ cp -r source destination。這種復(fù)制方法僅僅遞歸地復(fù)制文件和目錄。文件的權(quán)限、所有者和其他元數(shù)據(jù)并不會復(fù)制到目標(biāo)位置。可以使用 -p 選項(xiàng)保留復(fù)制的每個(gè)文件和目錄的所有者、權(quán)限和時(shí)間:$ cp -pr source destination。
使用 cp 命令是最容易最公認(rèn)的文件復(fù)制方法,但是 cp 的效率很低,而且如果不使用 NFS 這樣的遠(yuǎn)程文件系統(tǒng)解決方案,就不可能把目錄復(fù)制到遠(yuǎn)程系統(tǒng)上。
使用 tar
tar(tape archive 的簡寫)實(shí)用程序原來用于高效地把目錄結(jié)構(gòu)(包括文件和文件元數(shù)據(jù))轉(zhuǎn)換為二進(jìn)制流,然后就可以把二進(jìn)制流寫到備份磁帶上。
通常使用 tar 創(chuàng)建一個(gè)包含所需目錄的 .tar 文件:$ tar cf mydir.tar ./mydir。c 選項(xiàng)讓 tar 創(chuàng)建新的存檔文件,f 選項(xiàng)使用后面的參數(shù)指定要創(chuàng)建的存檔文件的名稱 (mydir.tar)。其余參數(shù)指定應(yīng)該包含在存檔文件中的文件或目錄。tar 命令自動地遞歸遍歷目錄結(jié)構(gòu),所以如果指定包含一個(gè)目錄,tar 將在存檔文件中包含這個(gè)目錄以及其中的所有文件和目錄。
tar 的一個(gè)重要特點(diǎn)是,用戶指定的路徑名被看作絕對路徑。也就是說,如果為 tar 指定完整的目錄位置,例如 /etc 目錄:$ tar cf etc.tar /etc。那么,在默認(rèn)情況下,tar 將把文件提取到它們的絕對位置。例如,如果提取這個(gè)存檔文件:$ tar xf etc.tar,就會在 /etc 目錄中重新創(chuàng)建文件和目錄結(jié)構(gòu)。這可能會產(chǎn)生破壞(可能會覆蓋 /etc 中希望保留的文件)。這個(gè)問題有兩種解決方法。第一種是使用 GNU tar,它支持通過 --strip-path 選項(xiàng)從提取的路徑中刪除元素。
另一個(gè)簡單的解決方法是進(jìn)入父目錄,然后使用相對路徑(見清單 1)。
清單 1. 進(jìn)入父目錄并使用相對路徑
$ cd / $ tar cf etc.tar ./etc
在提取存檔文件時(shí),會在相對位置重新創(chuàng)建文件。可以使用這種方法幫助同步目錄。因?yàn)?tar 創(chuàng)建目錄結(jié)構(gòu)的字節(jié)流,可以通過結(jié)合使用 tar 和管道把文件從一個(gè)位置復(fù)制到另一個(gè)位置:$ tar cf - ./etc |( cd /backup; tar xf - )。“- 指定 tar 應(yīng)該使用標(biāo)準(zhǔn)輸出(在寫時(shí))或標(biāo)準(zhǔn)輸入(在讀時(shí))。圓括號讓語句在一個(gè)子 shell 中執(zhí)行。看一下管道符前面的代碼,它創(chuàng)建文件的字節(jié)流并發(fā)送到標(biāo)準(zhǔn)輸出。在管道符后面,切換到另一個(gè)目錄,然后從標(biāo)準(zhǔn)輸入提取字節(jié)流。
要想保留文件的所有者和權(quán)限,可以使用 p 選項(xiàng)保留每個(gè)文件和目錄的元數(shù)據(jù):$ tar cfp - ./etc |( cd /backup; tar xfp - )。
掌握這種基本結(jié)構(gòu)之后,就可以執(zhí)行更復(fù)雜的操作。例如,可以只復(fù)制在特定時(shí)間之后修改過的文件:$ tar cf - --newer 20090101 ./etc |(cd /backup; tar xf - )。這個(gè)命令創(chuàng)建在 2009 年 1 月 1 日之后修改過的文件的拷貝。
通過與 rsh 或 ssh 結(jié)合使用,還可以把文件同步到遠(yuǎn)程主機(jī):$ tar cfp - ./etc |(ssh user@host -- tar xfp -)。按照這種方式使用 ssh 和 tar 是在遠(yuǎn)程主機(jī)上創(chuàng)建本地文件備份的好方法。但是,還有更高效的信息同步方法。
使用 rsync 進(jìn)行智能化同步
前面介紹的文件同步方法的主要問題是,它們會復(fù)制每個(gè)文件(和相關(guān)聯(lián)的目錄結(jié)構(gòu))。如果您打算創(chuàng)建信息的新拷貝,這就沒關(guān)系;但是,如果要同步兩個(gè)目錄中的信息,這種方法的效率很低。
假設(shè)一個(gè)目錄中有 10,000 個(gè)文件,它們占用 100GB 的空間。如果修改了一個(gè) 10MB 的文件,在使用 cp 或 tar 進(jìn)行同步時(shí),就必須重新復(fù)制所有 100GB 文件。對于備份,復(fù)制如此大量的信息是很過分的。我們希望盡可能快速有效地完成備份。顯然,如果知道哪些文件修改過了,就可以只復(fù)制這些文件,但是不總是能夠知道這一信息。
tar 的 --newer 選項(xiàng)的作用是有限的,因?yàn)楸仨氈郎弦淮涡薷牡臏?zhǔn)確時(shí)間。rsync 工具能夠解決這個(gè)問題。它會比較目錄結(jié)構(gòu)和各個(gè)文件,判斷源目錄和目標(biāo)目錄之間的差異。在查明哪些文件和目錄已經(jīng)修改了之后,它只把這些文件和目錄復(fù)制到目標(biāo)位置。另外,rsync 對各個(gè)文件使用相似的算法,只復(fù)制文件中修改過的部分。
按照最簡單的形式,可以使用 rsync 把一個(gè)目錄同步到一個(gè)新目錄,例如:$ rsync -r a b。這會創(chuàng)建新目錄 b,其中包含目錄 a 中目錄結(jié)構(gòu)的拷貝。-r 選項(xiàng)讓 rsync 遞歸遍歷目錄并復(fù)制整個(gè)目錄結(jié)構(gòu)。但是,如果目標(biāo)目錄已經(jīng)存在,就會在目標(biāo)目錄 b 中創(chuàng)建一個(gè)新目錄 a,其中包含文件的拷貝。這會有一些糟糕的副作用。例如,如果要把多個(gè)目錄復(fù)制到備份目錄,就會像清單 2 這樣做。
清單 2. 把多個(gè)目錄復(fù)制到備份目錄
$ mkdir backup $ rsync dira backup $ rsync dirb backup
清單 2 創(chuàng)建目錄 backup/dira,其中包含原來 dira 目錄的拷貝。它還創(chuàng)建目錄 backup/dirb,其中包含原來 dirb 目錄的拷貝。后面的情況就不一樣了:$ rsync dira backup/dira。在第一次使用時(shí),這個(gè)腳本的作用符合期望。但是,在第二次使用時(shí),rsync 會在指定的目標(biāo)目錄中創(chuàng)建目標(biāo)目錄,也就是創(chuàng)建 backup/dira/dira 目錄。這不僅沒有創(chuàng)建我們需要的結(jié)構(gòu),還造成了內(nèi)容重復(fù)(其中一個(gè)版本是沒有同步的)。
在使用 rsync 時(shí),可能需要指定另外幾個(gè)選項(xiàng)。默認(rèn)的同步并不復(fù)制文件元數(shù)據(jù),而且像對待普通文件那樣對待某些特殊文件(比如鏈接)。希望使用的主要選項(xiàng)包括:
●--delete —— 從目標(biāo)目錄中刪除源目錄中不再存在的文件。默認(rèn)模式僅僅同步文件修改并創(chuàng)建新文件。在默認(rèn)情況下,如果在源目錄中刪除了一個(gè)文件,就會忽略它,并不在目標(biāo)目錄中相應(yīng)地刪除它。通過使用這個(gè)選項(xiàng),可以創(chuàng)建完全相同的同步。
●--recursive —— 遞歸地復(fù)制目錄和文件。
●--times —— 同步每個(gè)文件和目錄的修改時(shí)間和創(chuàng)建時(shí)間。
●--owner —— 如果可能的話,保留文件的所有者。
●--group —— 如果可能的話,保留組所有者。
●--links —— 把符號鏈接復(fù)制為符號鏈接,而不是復(fù)制文件數(shù)據(jù)并解釋源鏈接。
●--perms —— 保留文件權(quán)限。
●--hard-links —— 保留硬鏈接(在目標(biāo)目錄中創(chuàng)建硬鏈接),而不是復(fù)制文件內(nèi)容。
其中一部分選項(xiàng)只能在兩個(gè)系統(tǒng)的配置完全相同的情況下使用。例如,只有在源和目標(biāo)計(jì)算機(jī)對相同用戶使用相同 ID 的情況下,才能保留文件所有者和組所有者設(shè)置。
除了本地復(fù)制之外,rsync 還可以使用 ssh 執(zhí)行遠(yuǎn)程復(fù)制。為此,需要在源目錄或目標(biāo)目錄前面指定用戶名和遠(yuǎn)程主機(jī)。例如,為了把一個(gè)目錄同步到遠(yuǎn)程系統(tǒng) user 上,執(zhí)行以下命令:$ rsync --recursive dira user@remote:/backup/dirb。如果沒有設(shè)置無密碼 ssh 連接,那么會提示您輸入遠(yuǎn)程密碼。如果已經(jīng)設(shè)置了連接,就可以用這種方法執(zhí)行無人值守的夜間備份。
還可以對源目錄使用相同的用戶/密碼組合,從而從遠(yuǎn)程源目錄復(fù)制到本地目錄:$ rsync --recursive user@remote:dira dirb。在通過 Internet 復(fù)制到遠(yuǎn)程系統(tǒng)時(shí),還可以使用 --compress 選項(xiàng)在通過網(wǎng)絡(luò)傳輸信息之前壓縮信息,與原始字節(jié)復(fù)制相比,這可以大大提高效率。當(dāng)然,在復(fù)制到遠(yuǎn)程系統(tǒng)時(shí),如果文件包含敏感信息,可能不希望復(fù)制原始文件。在這種情況下,就需要使用加密。
加密同步涉及的文件
使用文件同步解決方案的常見原因之一是,為了創(chuàng)建文件的精確備份,以便在出現(xiàn)問題時(shí)能夠復(fù)制或重建目錄結(jié)構(gòu)的元素。
rsync 工具非常適合完成這個(gè)任務(wù),因?yàn)樗粡?fù)制兩個(gè)目錄之間有差異的文件,效率很高。更有意義的是,因?yàn)?rsync 可以同步到遠(yuǎn)程系統(tǒng),所以可以使用它自動創(chuàng)建遠(yuǎn)程備份,不需要把備份文件單獨(dú)復(fù)制到遠(yuǎn)程系統(tǒng)。
這個(gè)過程的一個(gè)限制是,創(chuàng)建的拷貝是未加密的。如果要把文件復(fù)制到遠(yuǎn)程系統(tǒng),而其他人也能夠訪問這個(gè)遠(yuǎn)程系統(tǒng),就需要確保其他人無法讀取這些文件(即使他們能夠接觸到這些文件)。
只使用 rsync 是無法加密文件的。也無法使用 rsync 的算法只加密在上一次同步操作之后修改過的文件。
但是,通過在腳本中執(zhí)行 rsync,就可以用 rsync 的輸出創(chuàng)建文件的輔助拷貝,然后對這個(gè)拷貝進(jìn)行加密。
這個(gè)腳本的基本原理是創(chuàng)建原目錄結(jié)構(gòu)的兩個(gè)拷貝。第一個(gè)拷貝作為參照拷貝,其中包含目錄結(jié)構(gòu)的精確副本。這樣,當(dāng)再次同步目錄時(shí),就可以像一般情況一樣比較源和目標(biāo)文件并判斷出差異。在 rsync 命令中使用 --itemize-changes 選項(xiàng),rsync 就會創(chuàng)建一個(gè)參照列表,其中列出在同步期間每個(gè)文件所發(fā)生的情況。輸出詳細(xì)說明文件是否已經(jīng)修改過(或新建),或文件是否已經(jīng)刪除。清單 3 中給出一個(gè)示例。
清單 3. rsync 生成的修改記錄
.d..t...... t1/a/ *deleting t1/a/3 .d..t...... t1/b/ >f.st...... t1/b/1 >f+++++++++ t1/b/6
以 .d. 開頭的行表示新目錄或目錄修改。*deleting 行表示文件已經(jīng)從源目錄中刪除。>f 行表示文件已經(jīng)修改過或是新建的文件(>f++++++++)。
通過解析這個(gè)輸出文件,可以判斷出源目錄和目標(biāo)參照目錄之間的差異。判斷出差異之后,可以在第三個(gè)目錄中創(chuàng)建原文件的加密版本。通過使用修改記錄,只加密(或刪除)在上一次同步操作之后修改過的文件。不能使用目錄的加密版本直接執(zhí)行同步,因?yàn)槲募募用馨姹究偸桥c源文件不一樣。
完整的腳本見清單 4。
清單 4. 完整腳本
#!/usr/bin/perl use warnings; use strict; use File::Basename; use File::Path; my $source = shift; my $dest = shift; my $encdest = shift; if (!defined($source) || !defined($dest) || !defined($encdest)) { print "Error: Not enough arguments!n"; print "Usage: $0 source destination encrypteddestn"; exit(1); } print STDERR "Running rsync between $source and $dest ($encdest)n"; system("rsync --delete --recursive --times -og --links --perms " . "--hard-links --itemize-changes $source $dest " . ">/tmp/$$.rsynclog 2>&1"); open(DATA,"/tmp/$$.rsynclog") or dIE "Couldn't open the rsynclogn"; my @changedfiles; my @delfiles; while(<DATA>) { next if (m/sending incremental file list/); chomp; last if (length($_) == 0); my ($changes,$filename) = split; push @changedfiles,$filename if ($changes =~ m/^>f/); push @delfiles,$filename if ($changes =~ m/^*del/); } close(DATA); my $counter = 0; foreach my $file (@changedfiles) { if (-f "$dest/$file") { my $sourcename = encode_filename("$dest/$file"); my $destname = encode_filename("$encdest/$file"); my $dirname = dirname("$encdest/$file"); mkpath($dirname); system(sprintf('cat "%s" |openssl enc -des3 ' . '-pass file:/var/lib/passphrase -a >"%s"', $sourcename,$destname)); $counter++; } } my $delcounter = 0; foreach my $file (@delfiles) { unlink("$encdest/$file"); $delcounter++; } print STDERR "Finished (changed: $counter, deleted: $delcounter)n"; unlink("/tmp/$$.rsynclog"); sub encode_filename { my ($filename) = @_; $filename =~ s/ / /g; $filename =~ s/'/'/g; $filename =~ s/"/"/g; $filename =~ s/(/(/g; $filename =~ s/)/)/g; $filename =~ s/&/&/g; $filename =~ s/#/#/g; return($filename); }
這個(gè)腳本非常簡單,很容易使用。在運(yùn)行腳本時(shí),指定源目錄、參照文件的目標(biāo)目錄和文件加密版本的目標(biāo)目錄:$ rsyncrypt source destination destination.enc。
腳本的第一部分在源和目標(biāo)目錄之間執(zhí)行基本的同步以判斷修改(見清單 5)。這個(gè)操作生成記錄修改的文件(在 /tmp 目錄中)。
清單 5. 在源和目標(biāo)目錄之間執(zhí)行基本的同步
system("rsync --delete --recursive --times -og --links --perms " . "--hard-links --itemize-changes $source $dest " . ">/tmp/$$.rsynclog 2>&1");
接下來,解析修改的列表,生成已經(jīng)修改和刪除的文件的列表(見清單 6)。
清單 6. 解析修改的列表
while(<DATA>) { next if (m/sending incremental file list/); chomp; last if (length($_) == 0); my ($changes,$filename) = split; push @changedfiles,$filename if ($changes =~ m/^>f/); push @delfiles,$filename if ($changes =~ m/^*del/); }
對于每個(gè)修改過的文件,讀取參照版本并在加密目標(biāo)目錄中創(chuàng)建加密版本(見清單 7)。
清單 7. 創(chuàng)建每個(gè)修改過的文件的加密版本
foreach my $file (@changedfiles) { if (-f "$dest/$file") { my $sourcename = encode_filename("$dest/$file"); my $destname = encode_filename("$encdest/$file"); my $dirname = dirname("$encdest/$file"); mkpath($dirname); system(sprintf('cat "%s" |openssl enc -des3 ' . '-pass file:/var/lib/passphrase -a >"%s"', $sourcename,$destname)); $counter++; } }
因?yàn)橐褂?shell 執(zhí)行實(shí)際的加密,文件名必須進(jìn)行編碼。需要對一些特殊字符進(jìn)行轉(zhuǎn)義,否則 shell 會解釋它們。
對于實(shí)際的加密,使用 openssl 和一個(gè)簡單的文本文件(在 /var/lib/passphrase 中),這個(gè)文件包含信息編碼所用的密碼。還可以創(chuàng)建或使用專門生成的密鑰來執(zhí)行此操作或希望使用的其他加密命令。
最后,因?yàn)樵茨夸浿锌赡苡幸呀?jīng)刪除的文件,還要把刪除的文件從加密目錄內(nèi)容中刪除(見清單 8)。
清單 8. 把刪除的文件從加密目錄內(nèi)容中刪除
foreach my $file (@delfiles) { unlink("$encdest/$file"); $delcounter++; }
這個(gè)腳本非常有效,惟一的缺點(diǎn)是它需要信息的兩個(gè)拷貝(參照目錄和加密版本),而不只是一個(gè)。另外,為了簡化這個(gè)過程,并沒有把權(quán)限、所有者和時(shí)間戳信息同步到加密版本,但是很容易添加這個(gè)特性。因?yàn)檫@個(gè)腳本使用 rsync 生成修改的列表,這會顯著減少需要加密的文件數(shù)量,可以使用相同的優(yōu)化算法把新的文件加密版本同步到遠(yuǎn)程主機(jī),從而只傳輸在上一次同步操作之后修改過的加密文件。
結(jié)束語
本文討論了幾種不同的文件同步方法。基本的 cp 命令并不是真正的同步命令,但是可以用來執(zhí)行直接復(fù)制。對于真正的同步操作,cp 命令花費(fèi)的時(shí)間太長,效率很低。在使用 tar 時(shí),可以指定一個(gè)時(shí)間參照點(diǎn),只復(fù)制在這個(gè)時(shí)間點(diǎn)之后修改過的文件。但是,如果修改不明顯或無法通過簡單的比較查明,這個(gè)特性的意義也不大。
rsync 工具是更好的文件同步解決方案。它對源和目標(biāo)目錄執(zhí)行許多檢查和比較,可以實(shí)現(xiàn)高效的同步,甚至可以通過網(wǎng)絡(luò)或公共連接執(zhí)行同步。為了確保安全,可以結(jié)合使用 rsync 與加密技術(shù),確保在沒有正確的密碼或加密密鑰的情況下無法讀取遠(yuǎn)程文件。
