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种情况,需要我们注意:
如果要创建的目录是多级的,我们必须从父目录开始一级级创建,直接一次性创建会返回
Errno::ENOENT
错误(类似在Linux中你需要用mkdir -p
才能成功);如果要创建的目录已经存在,这个方法会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有点类似正则表达式,但并不完全相同,只有一些基本的逻辑。其中:
**
表示递归目录匹配*
匹配任意文件{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"]