计算机技术就像计算机本身一样飞速发展,这难道不有趣吗?一些做硬编码计算机编程的人们如今在早期Web迷人的时光里开始接触HTML和CGI。我就是其中之一。如果你也涉猎了叫做“Web设计”的伪码梦幻世界的话,你无疑会发现在这个时代绝大多数设计师会分属于两个阵营之一。第一阵营是采用一个所见即所得(WYSIWYG)的编辑器比如Dreamweaver来设计和发布网页。第二阵营使用诸如Emacs或Vim的文本编辑器来手工编码HTML,然后通过一个FTP客户端将完成的网页上传到一台Web服务器让世界看见并欣赏它,希望如此。第一阵营牺牲灵活性换取便利性,而第二阵营正好相反。没有哪一种方法是错的,但是也没有哪一种是完全正确的。
我试图写一个程序来做所有这些我需要手工来做的事情,但是更快更准确。我决定使用Ruby来编写自动化脚本。我试图使代码短小并具可维护性,因为稍后我还要加入一些其他特性。Ruby(作为动态类型的语言)让编写紧凑的代码变得简单,将困扰最小化。它虽是脚本语言,但是也面向对象。这使得我可以避免代码重复,从而比使用过程语言更为优雅。Ruby还拥有一个相当出色的开源SFTP库可供使用(Net::SFTP[0]),因此我不必自己动手编写。(SFTP是一个安全传输文件的网络协议。)
在这篇文章中,我将指导你逐步完成整个流程,创建这个程序自己的版本。文章中包含了完整的示例源代码,并带有逐行的代码分析。我邀请你参与进来并体验Ruby是如何能够轻易的将你的工作自动例行化的。
我们的程序有一个基本需求:它要连接到一台远程的SFTP服务器并上载我们的文件。不过我们也希望它可以做到仅上载那些本地修改过的文件,并能够在判断文件上载的时候可以递归的检查其子目录。
脚本预想的流程应该是:
显然步骤1到3可以通过Ruby的内建对象和Net::SFTP轻松搞定。步骤4很有趣。尽管Ruby的Dir类提供了一种递归访问子目录的方法,但是和我们需要的还有所不同。既然Ruby对语言的扩展非常简单,为什么我们不自己写一个方法?不仅仅是因为这样做会很有趣,而且我们还将学到扩展Ruby是多么的容易。
除了Ruby本身以外,我们依赖的仅仅是Net::SFTP和Net::SSH[1]这两个库了。幸运的是,这两个软件包都是可以通过Gem[2]来安装的。假定你已经在本地机器上安装了Ruby,并正确设置了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
让我们逐行看一下:
在我的系统上执行了脚本以后,产生的输出如下:
.
..
cgi-bin
etc
logs
public_html
temp
现在让我们看看脚本:
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
让我们逐行来看一下:
1. - 2. 加载Net::SSH和Net::SFTP。让我们完善脚本,实现递归访问子目录并处理包含需要上载文件的目录在远程服务器不存在的情况:
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'
哇呜!这个要比我们之前的版本长很多,但这主要是处理远程目录不存在的时候必须要做的异常检查造成的。这是Net::SFTP库的一个限制。当我们试图上载一个并不存在的远程目录的时候,put_file方法将会抛出一个令人讨厌的异常。put_file方法理应通过自动创建文件目录树中的缺失部分来处理这种情况。然而修改这个方法并不在本文讨论的范围以内,因此我把这个留给你作为练习。
让我们逐行来看一下新代码:
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
我们成功了!现在我们有了一个从任意深度的本地目录树上载文件到远程服务器上的快速、方便的方法。脚本相当的聪明,当目录不存在的时候会创建目录,而且只上载那些实际修改过的文件。
尽管我们开发的“骨架”级脚本已经相当有用,但是依然存在一些我们仅通过少许工作就可以实现的改进:
同时,算不得是改进的是,Net::SSH库支持公钥认证。运行PuTTY的Pageant应用程序(请看http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html)并添加你的密钥,然后从我们的脚本的Net::SSH.start语句中移除你的密码。现在你可以在不将密码明文储存的情况下上载文件,也不必每次连接远程服务器的时候输入密码了。真好!
随着时间的推移, 这个脚本已经为我节省了数十个小时,我不再需要手工使用FTP GUI,也不需要通过一个恼人的FTP命令行程序频繁的变换目录。我希望这个脚本对你的工作也起到同样的作用。如果是这样的话,我邀请你通过我的网站(http://www.matthewbass.com/)联系我并让我知晓。我也同样有兴趣听到你对改进此脚本的建议,或者你完成了一个漂亮的重构可以精简一到两行代码。尽情的使用Ruby吧!
1. Net::SFTP是一个基于SFTP安全传送文件的Ruby API。它是Net::SSH的一部分。
2. Net::SSH是一个通过secure shell访问资源的Ruby API。http://rubyforge.org/projects/net-ssh/
3. Gem是Ruby的包管理器。http://www.rubygems.org/
4. 用Ruby实现自动应用程序部署。http://manuals.rubyonrails.com/read/book/17
查看英文原文:Automating File Uploads with SSH and Ruby联系客服