Different methods in Ruby to manipulate directories and files
Ruby中目录和文件操作的几种方式
1. Create a directory
Method 1: Use Dir.mkdir
We can call Dir.mkdir to create a directory with an optional parameter to mark the permissions of the directory.
For example, if I want to create a ruby
subdirectory in the /Users/hegwin/Workspace
directory, then I can do this:
Dir.mkdir('/Users/hegwin/Workspace/ruby')
# => 0
After successful creation, Dir.mkdir
will return the integer 0
.
This method is similar to the Linux command mkdir
, with these 2 cases that we need to be aware of:
If the directory to be created is multi-level, we have to create it one level at a time starting from the parent directory. Directly creating it all at once will return
Errno::ENOENT
error (similar to how you need to usemkdir -p
in Linux to make it work);If the directory to be created already exists, this method will raise
Errno::EEXIST
.
In order for the code to work in both cases, Dir has another method that can help you: Dir.exists?
will determine if the directory exists. Also File.directory?(path)
may be needed to determine if the path is a directory.
Method 2: Use the FileUtils
class
FileUtils is part of Ruby Standard Lib, and you need to require 'fileutils '
to use it.
FileUtils.mkdir_p(list, mode: nil, noop: nil, verbose: nil)
will create the directories for you level by level, and you don't need to care if the directory exists or not, if it already exists, FileUtils will ignore the operation, e.g:
FileUtils.mkdir_p('/Users/hegwin/Workspace/ruby/rails/demo')
# => ["/Users/hegwin/Workspace/ruby/rails/demo"]
Also, unlike Dir.mkdir
, FileUtils
doesn’t return an integer 0, but an array of directory paths.
Both of them will throw Errno::EACCES
errors when they encounter insufficient operation privileges.
2. List the files and directories in a directory
Method 1: Use Dir.entries(path_name)
Dir.entries will return all the directories and subdirectories under the given path_name, if the given path_name does not exist, it will raise SystemCallError
. An example is as follows:
Dir.entries('/Users/hegwin')
# => ["." , "..." , "Music", ".docker", "Screenshots", ".bashrc", "Pictures", "Desktop" ...]
The returned content is an array of strings, and the encoding of the strings follows the system by default. Also, it lists .
and ..
directories, i.e. the current directory and the parent directory, sometimes we need to filter out these 2 directories.
Method 2: Use Pathname
class
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 is also one of Ruby's std-lib, use it with require 'pathname'
.
Pathname.children
returns an array of all the files and directories in a given directory, each element of the array is an instance of Pathname, so it is also possible to make use of the various instance methods of Pathname, such as Pathname#file?
and Pathname#directory?
(which I personally feel would look better than the File.file?
type of methods); where .
and ..
directories will be omitted.
3. Recursive directory search for files
Method 1: Write your own recursion
With the help of the Pathname class, it is easy to get the files and directories in a directory, and to determine whether they are directories or files. For example, we can go like this to recursively find all the *.mp3 files in a directory and its subdirectories.
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)
Define the find_mp3
method to accept an instance of Pathname. In the method content, we will call the Pathname#children
method to get the files and subdirectories in the directory, and recursively call the find_mp3
method if it is a directory.
Method 2: Using Dir.glob
mp3_files = Dir.glob('**/*.mp3')
Dir.glob(pattern) is a more common method, which can also be called with `Dir[pattern]
. For example, we often see this line of code in the rspec helper:
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
Its argument pattern is somewhat similar to a regular expression, but not exactly the same, with only some basic logic. Among them:
**
means recursive directory matching*
matches any file{string1,string2}
matches any of the strings in brackets
A few examples taken from the official documentation examples:
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"]