計(jì)算機(jī)技術(shù)就像計(jì)算機(jī)本身一樣飛速發(fā)展,這難道不有趣嗎?一些做硬編碼計(jì)算機(jī)編程的人們?nèi)缃裨谠缙赪eb迷人的時(shí)光里開(kāi)始接觸HTML和CGI。我就是其中之一。如果你也涉獵了叫做“Web設(shè)計(jì)”的偽碼夢(mèng)幻世界的話(huà),你無(wú)疑會(huì)發(fā)現(xiàn)在這個(gè)時(shí)代絕大多數(shù)設(shè)計(jì)師會(huì)分屬于兩個(gè)陣營(yíng)之一。第一陣營(yíng)是采用一個(gè)所見(jiàn)即所得(WYSIWYG)的編輯器比如Dreamweaver來(lái)設(shè)計(jì)和發(fā)布網(wǎng)頁(yè)。第二陣營(yíng)使用諸如Emacs或Vim的文本編輯器來(lái)手工編碼HTML,然后通過(guò)一個(gè)FTP客戶(hù)端將完成的網(wǎng)頁(yè)上傳到一臺(tái)Web服務(wù)器讓世界看見(jiàn)并欣賞它,希望如此。第一陣營(yíng)犧牲靈活性換取便利性,而第二陣營(yíng)正好相反。沒(méi)有哪一種方法是錯(cuò)的,但是也沒(méi)有哪一種是完全正確的。
我試圖寫(xiě)一個(gè)程序來(lái)做所有這些我需要手工來(lái)做的事情,但是更快更準(zhǔn)確。我決定使用Ruby來(lái)編寫(xiě)自動(dòng)化腳本。我試圖使代碼短小并具可維護(hù)性,因?yàn)樯院笪疫€要加入一些其他特性。Ruby(作為動(dòng)態(tài)類(lèi)型的語(yǔ)言)讓編寫(xiě)緊湊的代碼變得簡(jiǎn)單,將困擾最小化。它雖是腳本語(yǔ)言,但是也面向?qū)ο?。這使得我可以避免代碼重復(fù),從而比使用過(guò)程語(yǔ)言更為優(yōu)雅。Ruby還擁有一個(gè)相當(dāng)出色的開(kāi)源SFTP庫(kù)可供使用(Net::SFTP[0]),因此我不必自己動(dòng)手編寫(xiě)。(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類(lèi)提供了一種遞歸訪(fǎng)問(wèn)子目錄的方法,但是和我們需要的還有所不同。既然Ruby對(duì)語(yǔ)言的擴(kuò)展非常簡(jiǎn)單,為什么我們不自己寫(xiě)一個(gè)方法?不僅僅是因?yàn)檫@樣做會(huì)很有趣,而且我們還將學(xué)到擴(kuò)展Ruby是多么的容易。
除了Ruby本身以外,我們依賴(lài)的僅僅是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
gem install net-sftp --include-dependencies
1: require 'net/ssh'
2: require 'net/sftp'
3: Net::SSH.start('server', 'username', 'password') do |ssh|
4: ssh.sftp.connect do |sftp|
5: Dir.foreach('.') do |file|
6: puts file
7: end
8: end
9: end
讓我們逐行看一下:
在我的系統(tǒng)上執(zhí)行了腳本以后,產(chǎn)生的輸出如下:
.
..
cgi-bin
etc
logs
public_html
temp
現(xiàn)在讓我們看看腳本:
1: require 'net/ssh'
2: require 'net/sftp'
3: Net::SSH.start('server', 'username', 'password') do |ssh|
4: ssh.sftp.connect do |sftp|
5: Dir.foreach('.') do |file|
6: next if File.stat(file).directory?
7: begin
8: local_file_changed = File.stat(file).mtime > Time.at(sftp.stat(file).mtime)
9: rescue Net::SFTP::Operations::StatusException
10: not_uploaded = true
11: end
12: if not_uploaded or local_file_changed
13: puts "#{file} has changed and will be uploaded"
14: sftp.put_file(file, file)
15: end
16: end
17: end
18: end
讓我們逐行來(lái)看一下:
1. - 2. 加載Net::SSH和Net::SFTP。讓我們完善腳本,實(shí)現(xiàn)遞歸訪(fǎng)問(wèn)子目錄并處理包含需要上載文件的目錄在遠(yuǎn)程服務(wù)器不存在的情況:
1: require 'net/ssh'
2: require 'net/sftp'
3: require 'dir'
4:
5: local_path = 'C:\public_html'
6: remote_path = '/usr/jsmith/public_html'
7: file_perm = 0644
8: dir_perm = 0755
9:
10: puts 'Connecting to remote server'
11: Net::SSH.start('server', 'username', 'password') do |ssh|
12: ssh.sftp.connect do |sftp|
13: puts 'Checking for files which need updating'
14: Find.find(local_path) do |file|
15: next if File.stat(file).directory?
16: local_file = "#{dir}/#{file}"
17: remote_file = remote_path + local_file.sub(local_path, '')
18:
19: begin
20: remote_dir = File.dirname(remote_file)
21: sftp.stat(remote_dir)
22: rescue Net::SFTP::Operations::StatusException => e
23: raise unless e.code == 2
24: sftp.mkdir(remote_dir, :permissions => dir_perm)
25: end
26:
27: begin
28: rstat = sftp.stat(remote_file)
29: rescue Net::SFTP::Operations::StatusException => e
30: raise unless e.code == 2
31: sftp.put_file(local_file, remote_file)
32: sftp.setstat(remote_file, :permissions => file_perm)
33: next
34: end
35:
36: if File.stat(local_file).mtime > Time.at(rstat.mtime)
37: puts "Copying #{local_file} to #{remote_file}"
38: sftp.put_file(local_file, remote_file)
39: end
40: end
41: end
42:
43: puts ‘Disconnecting from remote server'
44: end
45: end
46:
47: puts 'File transfer complete'
哇嗚!這個(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)建文件目錄樹(shù)中的缺失部分來(lái)處理這種情況。然而修改這個(gè)方法并不在本文討論的范圍以?xún)?nèi),因此我把這個(gè)留給你作為練習(xí)。
讓我們逐行來(lái)看一下新代碼:
Connecting to remote server
Checking for files which need updating
Copying D:/html/index.php to /home/public_html/index.php
Copying D:/html/media.php to /home/public_html/media.php
Copying D:/html/contact.php to /home/public_html/contact.php
Copying D:/html/images/go.gif to /home/public_html/images/go.gif
Copying D:/html/images/stop.gif to /home/public_html/images/stop.gif
Copying D:/html/include/menu.php to /home/public_html/include/menu.php
Disconnecting from remote server
File transfer complete
我們成功了!現(xiàn)在我們有了一個(gè)從任意深度的本地目錄樹(shù)上載文件到遠(yuǎn)程服務(wù)器上的快速、方便的方法。腳本相當(dāng)?shù)穆斆?,?dāng)目錄不存在的時(shí)候會(huì)創(chuàng)建目錄,而且只上載那些實(shí)際修改過(guò)的文件。
盡管我們開(kāi)發(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.greenend.org.uk/~sgtatham/putty/download.html)并添加你的密鑰,然后從我們的腳本的Net::SSH.start語(yǔ)句中移除你的密碼。現(xiàn)在你可以在不將密碼明文儲(chǔ)存的情況下上載文件,也不必每次連接遠(yuǎn)程服務(wù)器的時(shí)候輸入密碼了。真好!
隨著時(shí)間的推移, 這個(gè)腳本已經(jīng)為我節(jié)省了數(shù)十個(gè)小時(shí),我不再需要手工使用FTP GUI,也不需要通過(guò)一個(gè)惱人的FTP命令行程序頻繁的變換目錄。我希望這個(gè)腳本對(duì)你的工作也起到同樣的作用。如果是這樣的話(huà),我邀請(qǐng)你通過(guò)我的網(wǎng)站(http://www.matthewbass.com/)聯(lián)系我并讓我知曉。我也同樣有興趣聽(tīng)到你對(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訪(fǎng)問(wèn)資源的Ruby API。http://rubyforge.org/projects/net-ssh/
3. Gem是Ruby的包管理器。http://www.rubygems.org/
4. 用Ruby實(shí)現(xiàn)自動(dòng)應(yīng)用程序部署。http://manuals.rubyonrails.com/read/book/17
查看英文原文:Automating File Uploads with SSH and Ruby聯(lián)系客服