方便的文件树遍历

我经常会遇到进行批量文件修改的情况。Windows 脚本我十分不精通,以前都是靠现写一个 C# 程序。遇到 Ruby 后,我十分喜欢它语法上的灵活性。(虽然我认为太灵活不一定好)而且它还是一种脚本语言,很方便。考虑到我所遇到的情况,我想写一个类来支持对文件名(文件夹名)或全路径名进行正则表达式匹配。同时也支持反向过滤。即,保留那些没有匹配上的文件或文件夹。

举一个例子。比如用备份工具备份“我的文档”再还原后,很多隐藏文件现在都会显现出来。比如 thumbs.db 和 picasa.ini(因为我用 Google 的 Picasa)。我想把 picasa.ini 重新隐藏起来并删除 thumbs.db。可以这样写:

ftree = FileTree.new("c:\documents and settings\username\my documents")
free.traverse(
[
/^picasa.ini$/i,
/^thumbs.db$/i
],
{
:entry_type => :file
:for_basename_only => true
}
) do |file|
if file =~ /picasa/i
`attrib +h #{file}`
else
`attrib -s -h -r #{file}`
`del #{file}`
end
end

我认为这样还算是比较容易的吧 :) 如果只是想遍历一下,更简单,直接
FileTree.new("c:\dummy").traverse /file_name_pattern/


FileTree的源代码如下:

# author: Yang Dong
# date: 2008-6-15
#
# this class is designed to convient the traverse of file trees. you can
# just output the whole structure or you can specify some regular expressions
# to filter the unwanted files or directories, and customize the actions
# against them, plus some additional controls.
#
# examples to use (based on windows os):
# 1) say, you want to see the whole file structure. just write:
# FileTree.new("c:\dummy_directory").traverse
#
# 2) say, you want to hide all the picasa.ini files, write:
# ftree = FileTree.new("c:\dummy_dir")
# ftree.traverse(
# /^picasa.ini$/i,
# {
# :entry_type => :file,
# :for_basename_only => true
# }
# ) do |file|
# `attrib \"#{file}\" -s -h -r`
# end
class FileTree
require "pathname"

def initialize(dir)
@dir = dir.chomp.gsub(/\\/, '/')
pn = Pathname.new(@dir)
pn.cleanpath
raise "no such directory" unless pn.exist? && pn.directory?
end

# traverse the given directory. use filter_patterns to specify
# what kind of file name you would like to match. attach a
# block if you want to give some actions against the matched
# files other than just put them out on the standard out.
# the block takes one argument indicating the absolute file path
# of the matched one.
#
# the filter_patterns is an array containing regular expression
# objects.
#
# the options give some additional control over filtering.
# for details about filter_patterns and options, refer to the
# filter method.
#
# caution: the patterns and actions will not be applied to the root folder
# given.
def traverse(filter_patterns = nil, options = nil, &block)
trav @dir, filter_patterns, options, &block
end

private
def trav(dir, filter_patterns = nil, options = nil, &block)
pn = Pathname.new(dir)
children = pn.children

children.each do |child|
if filter(child, filter_patterns, options)
if block
block.call child.realpath.to_s
else
puts child.realpath.to_s
end
end

if child.exist? and child.directory?
trav child.realpath.to_s, filter_patterns, options, &block
end
end
end

# filters the given entry. if entry passed the filter, returns true.
# otherwise false.
#
# the filter_patterns is an array containing regular expression
# objects.
#
# options is a hash which supports the following options:
# entry_type:
# use this to specify to filter file or directory. if you only want
# to do something with files, then use { :entry_type => :file }.
# otherwise, use { :entry_type => :dir }. default is nil, which means
# either will be okay.
# exclude_matched:
# specify true to indicate that the matched file entries (including
# directories) will not pass the filter. this can be used when you want
# to do something with most of the entries in your folder but with some
# exceptions. default is set to false.
# for_basename_only:
# indicates whether the regular expression pattern will be comparing with
# the directory or file name only. the default is false, which means not
# only the name will be compared, but also the whole path will be
# compared.
#
def filter(entry, filter_patterns = nil, options = nil)
# defines a series of default options.
options = {} if options.nil?
if options[:entry_type] == :file
return false unless entry.file?
elsif options[:entry_type] == :dir
return false unless entry.directory?
end

filter_patterns = [ // ] if filter_patterns == nil
unless filter_patterns.is_a?(Array)
filter_patterns = ([] << filter_patterns)
end

filter_patterns.each do |filter_pattern|
if options[:exclude_matched]
if options[:for_basename_only]
return false if entry.basename.to_s =~ filter_pattern
else
return false if entry.realpath.to_s =~ filter_pattern
end
else
if options[:for_basename_only]
return true if entry.basename.to_s =~ filter_pattern
else
return true if entry.realpath.to_s =~ filter_pattern
end
end
end

if options[:exclude_matched]
return true
else
return false
end
end
end

有点长,不过一半是注释。如果有的地方的意图看不明白,可以参考下面的测试代码。测试使用与测试代码文件同级的一个“test_folder”文件夹。它的目录结构如下:

C:/netbeans-proj/file_tree/test/test_folder/test
C:/netbeans-proj/file_tree/test/test_folder/test/readme.txt
C:/netbeans-proj/file_tree/test/test_folder/test/src
C:/netbeans-proj/file_tree/test/test_folder/test/src/Assert.java
C:/netbeans-proj/file_tree/test/test_folder/test/src/Entry.java

如果要运行此测试,要先把这个文件结构构造出来才可以。也请保证“test_folder”的上级目录中没有包含src、assert、entry、readme这几个字符串的。不然,测试可能会出问题。

require 'test/unit'
require "file_tree"

class FileTreeTest < Test::Unit::TestCase
def setup
@root = "#{File.dirname(__FILE__).gsub(/\\/, "/")}/test_folder"
@file_tree = FileTree.new(@root)
end

def test_simple_traverse
output = ""
@file_tree.traverse do |entry|
output += "#{entry}\n"
end

expected_output = <<TAG
#{@root}/test
#{@root}/test/readme.txt
#{@root}/test/src
#{@root}/test/src/Assert.java
#{@root}/test/src/Entry.java
TAG
assert_equal expected_output, output
end

def test_entry_type
output = ""
@file_tree.traverse(nil, :entry_type => :file) do |file|
output += "#{file}\n"
end

expected_output = <<TAG
#{@root}/test/readme.txt
#{@root}/test/src/Assert.java
#{@root}/test/src/Entry.java
TAG
assert_equal expected_output, output

##########################################

output = ""
@file_tree.traverse(nil, :entry_type => :dir) do |dir|
output += "#{dir}\n"
end

expected_output = <<TAG
#{@root}/test
#{@root}/test/src
TAG
assert_equal expected_output, output
end

def test_exclude_matched
output = ""
@file_tree.traverse(nil, :exclude_matched => true) do |entry|
output += "#{entry}\n"
end
assert_equal "", output

###############################################

output = ""
@file_tree.traverse(/src/, :exclude_matched => true) do |entry|
output += "#{entry}\n"
end

expected_output = <<TAG
#{@root}/test
#{@root}/test/readme.txt
TAG
assert_equal expected_output, output
end

def test_for_basename_only
output = ""
@file_tree.traverse(/src/, :for_basename_only => true) do |entry|
output += "#{entry}\n"
end

expected_output = <<TAG
#{@root}/test/src
TAG
assert_equal expected_output, output
end

def test_multiple_patterns
output = ""
@file_tree.traverse [ /assert/i, /readme/i ] do |entry|
output += "#{entry}\n"
end

expected_output = <<TAG
#{@root}/test/readme.txt
#{@root}/test/src/Assert.java
TAG
assert_equal expected_output, output
end

def test_complicated_traverse
output = ""
@file_tree.traverse(
[
/assert/i,
/readme/i
],
{
:entry_type => :file,
:exclude_matched => true,
:for_basename_only => true
}
) do |file|
output += "#{file}\n"
end

expected_output = <<TAG
#{@root}/test/src/Entry.java
TAG
assert_equal expected_output, output
end
end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值