|
計(jì)算機(jī)技術(shù)就像計(jì)算機(jī)本身一樣飛速發(fā)展,這難道不有趣嗎?一些做硬編碼計(jì)算機(jī)編程的人們?nèi)缃裨谠缙赪eb迷人的時(shí)光里開始接觸HTML和CGI。我就是其中之一。如果你也涉獵了叫做“Web設(shè)計(jì)”的偽碼夢(mèng)幻世界的話,你無(wú)疑會(huì)發(fā)現(xiàn)在這個(gè)時(shí)代絕大多數(shù)設(shè)計(jì)師會(huì)分屬于兩個(gè)陣營(yíng)之一。第一陣營(yíng)是采用一個(gè)所見即所得(WYSIWYG)的編輯器比如Dreamweaver來(lái)設(shè)計(jì)和發(fā)布網(wǎng)頁(yè)。第二陣營(yíng)使用諸如Emacs或Vim的文本編輯器來(lái)手工編碼HTML,然后通過(guò)一個(gè)FTP客戶端將完成的網(wǎng)頁(yè)上傳到一臺(tái)Web服務(wù)器讓世界看見并欣賞它,希望如此。第一陣營(yíng)犧牲靈活性換取便利性,而第二陣營(yíng)正好相反。沒(méi)有哪一種方法是錯(cuò)的,但是也沒(méi)有哪一種是完全正確的。 在我早年的Web設(shè)計(jì)中我屬于第一陣營(yíng)。最近我擁抱了使用寫字板的命運(yùn)并加入了那些“手工制作”的隊(duì)伍。我喜歡這樣做所帶來(lái)的靈活性上的收益,但是也付出了損失易用性的代價(jià)。在本地安裝Web服務(wù)器并直接編輯文件不但浪費(fèi)時(shí)間而且不具移植性,所以我通常修改一個(gè)頁(yè)面、轉(zhuǎn)換到FTP客戶端、上載文件、轉(zhuǎn)換到瀏覽器并刷新以查看上載后的文件。這事做起來(lái)不算快,而且還需要反復(fù)做。這就是一個(gè)高喊著“把我自動(dòng)化”的過(guò)程。所以在上個(gè)夏天,我啟動(dòng)了最喜歡的文本編輯器并決定開始動(dòng)手。
我試圖寫一個(gè)程序來(lái)做所有這些我需要手工來(lái)做的事情,但是更快更準(zhǔn)確。我決定使用Ruby來(lái)編寫自動(dòng)化腳本。我試圖使代碼短小并具可維護(hù)性,因?yàn)樯院笪疫€要加入一些其他特性。Ruby(作為動(dòng)態(tài)類型的語(yǔ)言)讓編寫緊湊的代碼變得簡(jiǎn)單,將困擾最小化。它雖是腳本語(yǔ)言,但是也面向?qū)ο蟆_@使得我可以避免代碼重復(fù),從而比使用過(guò)程語(yǔ)言更為優(yōu)雅。Ruby還擁有一個(gè)相當(dāng)出色的開源SFTP庫(kù)可供使用(Net::SFTP[0]),因此我不必自己動(dòng)手編寫。(SFTP是一個(gè)安全傳輸文件的網(wǎng)絡(luò)協(xié)議。) 在這篇文章中,我將指導(dǎo)你逐步完成整個(gè)流程,創(chuàng)建這個(gè)程序自己的版本。文章中包含了完整的示例源代碼,并帶有逐行的代碼分析。我邀請(qǐng)你參與進(jìn)來(lái)并體驗(yàn)Ruby是如何能夠輕易的將你的工作自動(dòng)例行化的。 需求我們的程序有一個(gè)基本需求:它要連接到一臺(tái)遠(yuǎn)程的SFTP服務(wù)器并上載我們的文件。不過(guò)我們也希望它可以做到僅上載那些本地修改過(guò)的文件,并能夠在判斷文件上載的時(shí)候可以遞歸的檢查其子目錄。 腳本預(yù)想的流程應(yīng)該是:
顯然步驟1到3可以通過(guò)Ruby的內(nèi)建對(duì)象和Net::SFTP輕松搞定。步驟4很有趣。盡管Ruby的Dir類提供了一種遞歸訪問(wèn)子目錄的方法,但是和我們需要的還有所不同。既然Ruby對(duì)語(yǔ)言的擴(kuò)展非常簡(jiǎn)單,為什么我們不自己寫一個(gè)方法?不僅僅是因?yàn)檫@樣做會(huì)很有趣,而且我們還將學(xué)到擴(kuò)展Ruby是多么的容易。 依賴除了Ruby本身以外,我們依賴的僅僅是Net::SFTP和Net::SSH[1]這兩個(gè)庫(kù)了。幸運(yùn)的是,這兩個(gè)軟件包都是可以通過(guò)Gem[2]來(lái)安裝的。假定你已經(jīng)在本地機(jī)器上安裝了Ruby,并正確設(shè)置了Gem的路徑,然后在命令提示符前輸入: gem install net-ssh --include-dependencies 編碼開始!現(xiàn)在我們準(zhǔn)備開始寫點(diǎn)兒代碼。我們先來(lái)嘗試連接遠(yuǎn)程服務(wù)器、列出一個(gè)給定本地目錄的所有文件并關(guān)閉連接。我們將會(huì)使用Net::SSH和Net::SFTP接口以及Ruby的Dir類來(lái)實(shí)現(xiàn)。 1: require 'net/ssh' 讓我們逐行看一下:
在我的系統(tǒng)上執(zhí)行了腳本以后,產(chǎn)生的輸出如下: . 回頭看看我們最初的需求列表,我們只用了9行代碼就完成了步驟1和步驟2。 下面來(lái)到步驟3,比較列出文件的時(shí)間戳和其所對(duì)應(yīng)的遠(yuǎn)程時(shí)間戳,僅僅上載那些修改過(guò)的文件。(為了實(shí)現(xiàn)這個(gè)目的,我們定義一個(gè)文件是否修改過(guò)為本地時(shí)間戳大于等于遠(yuǎn)程服務(wù)器上的時(shí)間戳。)使用Ruby比較時(shí)間戳想當(dāng)容易。實(shí)際上,比較本身只用一行代碼就能夠做到。 現(xiàn)在讓我們看看腳本: 1: require 'net/ssh' 讓我們逐行來(lái)看一下: 1. - 2. 加載Net::SSH和Net::SFTP。3. - 4. 建立SSH會(huì)話和SFTP連接。 5. 遍歷當(dāng)前工作目錄下的所有文件。 6. 由于當(dāng)前我們還不能遞歸訪問(wèn)目錄,所以需要查看當(dāng)前文件是否是一個(gè)目錄。如果是的話則跳過(guò)到循環(huán)的下個(gè)迭代。 7. - 11. 由于遠(yuǎn)程文件可能不存在, 所以我們需要捕捉當(dāng)我們?cè)噲D去查看一個(gè)不存在的文件的時(shí)間戳的時(shí)候Net::SFTP拋出的異常。我們?cè)O(shè)置了兩個(gè)標(biāo)志,一個(gè)用于標(biāo)記本地文件是否修改過(guò)并需要上載,另一個(gè)用于標(biāo)記遠(yuǎn)程文件是否存在。 12. - 13. 如果本地文件沒(méi)有被上載過(guò)或者比遠(yuǎn)程文件要新的話,打印一行文字說(shuō)明文件將被上載。 14. 上載本地文件到遠(yuǎn)程服務(wù)器。 15. - 18. 結(jié)束if語(yǔ)句、文件循環(huán)、SFTP連接和SSH會(huì)話。 現(xiàn)在我們已經(jīng)完成了三個(gè)需求。我們現(xiàn)在的腳本可以登錄到遠(yuǎn)程服務(wù)器并上載所有本地系統(tǒng)中修改過(guò)的文件,但是腳本還只是僅僅能處理單一目錄。它還不能盡如子目錄去查看需要上載的文件。它也不能創(chuàng)建遠(yuǎn)程服務(wù)器上沒(méi)有的目錄。我們會(huì)在在我們宣布腳本完成以前解決這兩個(gè)問(wèn)題。 遞歸讓我們完善腳本,實(shí)現(xiàn)遞歸訪問(wèn)子目錄并處理包含需要上載文件的目錄在遠(yuǎn)程服務(wù)器不存在的情況: 1: require 'net/ssh' 哇嗚!這個(gè)要比我們之前的版本長(zhǎng)很多,但這主要是處理遠(yuǎn)程目錄不存在的時(shí)候必須要做的異常檢查造成的。這是Net::SFTP庫(kù)的一個(gè)限制。當(dāng)我們?cè)噲D上載一個(gè)并不存在的遠(yuǎn)程目錄的時(shí)候,put_file方法將會(huì)拋出一個(gè)令人討厭的異常。put_file方法理應(yīng)通過(guò)自動(dòng)創(chuàng)建文件目錄樹中的缺失部分來(lái)處理這種情況。然而修改這個(gè)方法并不在本文討論的范圍以內(nèi),因此我把這個(gè)留給你作為練習(xí)。 讓我們逐行來(lái)看一下新代碼: 5. - 6. 定義變量用于設(shè)定將會(huì)被比較和上載的本地和遠(yuǎn)程的目錄。 7. - 8. 定義變量用于設(shè)定當(dāng)遠(yuǎn)程服務(wù)器不存在的時(shí)候我們需要設(shè)定的文件和目錄的默認(rèn)權(quán)限。 9. - 13. 建立到遠(yuǎn)程服務(wù)器的SSH會(huì)話和SFTP連接。 14. 開始遞歸訪問(wèn)每個(gè)子目錄。 15. 循環(huán)當(dāng)前目錄下的每個(gè)條目。 15. 如果當(dāng)前條目是目錄而非文件則跳過(guò)至下一條目。 16. 設(shè)置local_file變量為我們遍歷到的本地文件,相對(duì)于我們的當(dāng)前目錄。 17. 設(shè)置remote_file變量為遠(yuǎn)程服務(wù)器的目標(biāo)路徑,并前綴remote_dir路徑以便我們可以上傳至正確的地址而不是用戶的home目錄。 19. - 26. 這是另一段惹人厭的代碼,如果Net::SFTP能在文件處理方面再智能些這些代碼就不會(huì)出現(xiàn)在這里。我們需要檢查我們?cè)噲D上載的遠(yuǎn)程目錄是否已經(jīng)存在。為了實(shí)現(xiàn)這個(gè)功能,我們調(diào)用sftp.stat(..)并傳入待檢查的目錄名。如果stat拋出一個(gè)異常且屬性碼是2,則說(shuō)明遠(yuǎn)程目錄不存在。我們則需要?jiǎng)?chuàng)建它并設(shè)置正確的權(quán)限。 27. - 35. 更令人厭惡的代碼,用于檢查即將上載的遠(yuǎn)程文件是否存在。然后其實(shí)我們可以不必做這個(gè)檢查就創(chuàng)建遠(yuǎn)程文件,因?yàn)楫?dāng)我們上載文件的時(shí)候會(huì)自動(dòng)創(chuàng)建。但是我們需要做這個(gè)檢查,因?yàn)楫?dāng)上傳一個(gè)新文件時(shí)我們要給遠(yuǎn)程文件設(shè)置適當(dāng)?shù)臋?quán)限。如果我們不這么做的話則會(huì)使用默認(rèn)的UNIX權(quán)限,這樣做可能會(huì)禁止我們稍后的文件上載。 36. - 40. 最后,如果到了這里,說(shuō)明將要上載的遠(yuǎn)程目錄和文件都存在。我們比較本地文件和遠(yuǎn)程文件的修改時(shí)間。如果本地文件更新,則上載它。 40. - 48. 所有我們開啟的循環(huán)的結(jié)束,關(guān)閉和遠(yuǎn)程服務(wù)器的SFTP連接和SSH會(huì)話。 這是腳本執(zhí)行的時(shí)候可能產(chǎn)生的輸出: Connecting to remote server 我們成功了!現(xiàn)在我們有了一個(gè)從任意深度的本地目錄樹上載文件到遠(yuǎn)程服務(wù)器上的快速、方便的方法。腳本相當(dāng)?shù)穆斆?,?dāng)目錄不存在的時(shí)候會(huì)創(chuàng)建目錄,而且只上載那些實(shí)際修改過(guò)的文件。 改進(jìn)的方向盡管我們開發(fā)的“骨架”級(jí)腳本已經(jīng)相當(dāng)有用,但是依然存在一些我們僅通過(guò)少許工作就可以實(shí)現(xiàn)的改進(jìn):
同時(shí),算不得是改進(jìn)的是,Net::SSH庫(kù)支持公鑰認(rèn)證。運(yùn)行PuTTY的Pageant應(yīng)用程序(請(qǐng)看http://www.chiark./~sgtatham/putty/download.html)并添加你的密鑰,然后從我們的腳本的Net::SSH.start語(yǔ)句中移除你的密碼?,F(xiàn)在你可以在不將密碼明文儲(chǔ)存的情況下上載文件,也不必每次連接遠(yuǎn)程服務(wù)器的時(shí)候輸入密碼了。真好! 總結(jié)隨著時(shí)間的推移, 這個(gè)腳本已經(jīng)為我節(jié)省了數(shù)十個(gè)小時(shí),我不再需要手工使用FTP GUI,也不需要通過(guò)一個(gè)惱人的FTP命令行程序頻繁的變換目錄。我希望這個(gè)腳本對(duì)你的工作也起到同樣的作用。如果是這樣的話,我邀請(qǐng)你通過(guò)我的網(wǎng)站(http://www./)聯(lián)系我并讓我知曉。我也同樣有興趣聽到你對(duì)改進(jìn)此腳本的建議,或者你完成了一個(gè)漂亮的重構(gòu)可以精簡(jiǎn)一到兩行代碼。盡情的使用Ruby吧! 注腳1. Net::SFTP是一個(gè)基于SFTP安全傳送文件的Ruby API。它是Net::SSH的一部分。 2. Net::SSH是一個(gè)通過(guò)secure shell訪問(wèn)資源的Ruby API。http:///projects/net-ssh/ 3. Gem是Ruby的包管理器。http://www./ 4. 用Ruby實(shí)現(xiàn)自動(dòng)應(yīng)用程序部署。http://manuals./read/book/17 查看英文原文:Automating File Uploads with SSH and Ruby |
|
|