Hegwin.Me

Thoughts grows up by feeding itself with its own words.

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:

  1. 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 use mkdir -p in Linux to make it work);

  2. 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:

  1. ** means recursive directory matching
  2. * matches any file
  3. {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"]
< Back