Hegwin.Me

南朝四百八十寺,多少楼台烟雨中。

Ruby中目录和文件操作的几种方式

Different methods in Ruby to manipulate directories and files

1. 创建目录

方法一:利用 Dir.mkdir

Dir.mkdir 可以创建一个目录,并且其有一个可选参数用来标记目录的权限。

如我要 在/Users/hegwin/Workspace 目录下创建一个 ruby 子目录,那么可以这么操作:

Dir.mkdir('/Users/hegwin/Workspace/ruby')
# => 0

正常创建后,Dir.mkdir 会返回整数 0

这个方法和Linux命令中 mkdir 类似,有这么2种情况,需要我们注意:

  1. 如果要创建的目录是多级的,我们必须从父目录开始一级级创建,直接一次性创建会返回 Errno::ENOENT 错误(类似在Linux中你需要用 mkdir -p 才能成功);

  2. 如果要创建的目录已经存在,这个方法会raise Errno::EEXIST

为了在这两种情况下,代码能正常运行,Dir有另一个方法可以帮助你: Dir.exist? 会去判断目录是否存在。另外可能还需要 File.directory?(path) 去判断路径是否是目录。

方法二:利用 FileUtils

FileUtils 是 Ruby Standard Lib 中的一部分,你需要在代码中 require 'fileutils' 才能使用它。

FileUtils.mkdir_p(list, mode: nil, noop: nil, verbose: nil)会帮你逐级创建目录,也无需关心目录是否存在,如果已经存在,FileUtils会忽视这个操作,如:

FileUtils.mkdir_p('/Users/hegwin/Workspace/ruby/rails/demo')
# => ["/Users/hegwin/Workspace/ruby/rails/demo"]

此外,与 Dir.mkdir 不同的是,FileUtils 的返回不是整数0,而是目录path的数组。

当遇到操作权限不足时,二者都会抛出 Errno::EACCES 错误。

2. 列出目录下的文件和目录

方法一:利用 Dir.entries(path_name)

Dir.entries会返回给定path_name下的所有目录和子目录,如果给定path_name不存在,会 raise SystemCallError。举例如下:

Dir.entries('/Users/hegwin')
# => [".", "..", "Music", ".docker", "Screenshots", ".bashrc", "Pictures", "Desktop" ...]

返回的内容是一个字符串数组,字符串的编码默认跟随系统。同时,它也会列出...目录,即当前目录和上级目录,有时候我们需要过滤掉这2个目录。

方法二:利用 Pathname

require 'pathname'

path = Pathname.new('/Users/hegwin')
# => #<Pathname:/Users/hegwin>
path.children
# =>  [#<Pathname:/Users/hegwin/Music>, #<Pathname:/Users/hegwin/.docker>, #<Pathname:/Users/hegwin/Screenshots>, #<Pathname:/Users/hegwin/.bashrc>, #<Pathname:/Users/hegwin/Pictures>, #<Pathname:/Users/hegwin/Desktop> ...]

Pathname也是Ruby的std-lib之一,使用时需要require 'pathname'

Pathname.children会返回一个数组,包含给定目录下的所有文件和目录,数组中的每一个元素都是一个Pathname的实例,因此也可以利用Pathname的各种实例方法,如 Pathname#file?Pathname#directory? (个人感觉会比 File.file? 这类方法要好看) ;其中...目录都会被省略。

3. 递归目录查找文件

方法一:自己写递归

在Pathname类的帮助下,我们很容易得到一个目录下的文件和目录,也很容易判断它们是目录还是文件。比如我们可以这样去递归查找一个目录及其子目录下所有的 *.mp3 文件。

require 'pathname'

def find_mp3(path)
  path.children.collect do |child|
    if child.file? && File.extname(child).end_with?('.mp3')
      child
    elsif child.directory?
      find_mp3(child)
    end
  end.flatten.compact
end

music_path = Pathname.new(music_dir) 
mp3_files = find_mp3(music_path)

定义find_mp3方法,接受一个Pathname的实例。在方法内容,我们会调用Pathname#children方法,取得目录下的文件和子目录,如果是目录则递归调用find_mp3方法。

方法二:利用 Dir.glob

mp3_files = Dir.glob('**/*.mp3') 

Dir.glob(pattern)是一个更为常用的方法,也可以用Dir[pattern]的方式去调用这个方法。比如我们在rspec helper中经常看到这行代码:

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

其参数pattern有点类似正则表达式,但并不完全相同,只有一些基本的逻辑。其中:

  1. ** 表示递归目录匹配
  2. * 匹配任意文件
  3. {string1,string2}匹配花括号中任意一个字符串

从官方文档的实例里摘抄了几个例子:

Dir.glob("*")                       #=> ["config.h", "main.rb"]
Dir.glob("config.?")                #=> ["config.h"]
Dir["config.?"]                     #=> ["config.h"]
Dir.glob("*.[a-z][a-z]")            #=> ["main.rb"]
Dir.glob("*.[^r]*")                 #=> ["config.h"]
Dir.glob("*.{rb,h}")                #=> ["main.rb", "config.h"]
< Back