Video transcript & code
In the last episode we used the
File module to extract parts of filenames, like the directory, basename, and extension. The
File module is fine for occasional filename-related usage. But when we have a lot of work having to do with filenames and paths, it becomes a little tedious to put
File. in front of everything.
That's where the
Pathname library comes in, which is the subject of today's episode.
pathname is a sort of melting pot for Ruby filename-related methods, combining methods from several different modules including the
Let's start with how to make a pathname. First, we need to require the library. Then we can make a
require "pathname" Pathname.new("~/.emacs") # => #<Pathname:~/.emacs>
But there's a shortcut. Instead of
.new, we can use the
Pathname() conversion function.
require "pathname" Pathname("~/.emacs") # => #<Pathname:~/.emacs>
This is my preferred method for creating pathnames.
Now let's take a look at what we can do with this method.
First of all, we can expand paths to be fully-qualified.
require "pathname" path = Pathname("193-pathname.org") path = path.expand_path # => #<Pathname:/home/avdi/Dropbox/rubytapas/193-pathname/193-pathname.org>
We have access to
extname as instance methods. These work just like their File counterparts.
require "pathname" path = Pathname("193-pathname.org") path = path.expand_path path.dirname # => #<Pathname:/home/avdi/Dropbox/rubytapas/193-pathname> path.basename # => #<Pathname:193-pathname.org> path.extname # => ".org"
Pathname also has some handy methods for cleaning up paths. For instance, if we have a path with a bunch of extra slashes or dots in it, we can send
#cleanpath to it to get a cleaned-up version.
require "pathname" Pathname("/home////avdi/./.emacs").cleanpath # => #<Pathname:/home/avdi/.emacs>
There is also a helper for resolving symbolic links. Let's say we have a file, "FOO", and a symbolic link to that file called
LINK_TO_FOO. When create a
LINK_TO_FOO and then expand and clean it, the resulting
Pathname is still for
LINK_TO_FOO. But if we instead use
realpath, the resulting expanded pathname is for
FOO, the target of the link.
require "pathname" Pathname("LINK_TO_FOO").expand_path.cleanpath # => #<Pathname:/home/avdi/Dropbox/rubytapas/193-pathname/LINK_TO_FOO> Pathname("LINK_TO_FOO").realpath # => #<Pathname:/home/avdi/Dropbox/rubytapas/193-pathname/FOO>
Pathname makes it easy to query for various properties of a file. We can test if it is a file, or if it is a directory. We can check its size on disk, or ask when it was last modified. This is just a sampling of the metadata that's available; see the documentation for a complete list.
require "pathname" path = Pathname("193-pathname.org") path.file? # => true path.directory? # => false path.size # => 2835 path.mtime # => 2014-03-02 14:59:04 -0500
Making filesystem changes is easy as well. We can copy files, rename them, and delete them. We can even make whole directory trees and remove them.
require "pathname" bar = Pathname("BAR") bar.write("Hello, world!") bar.rename("BAZ") Pathname("BAZ").exist? # => true Pathname("BAZ").delete Pathname("BAZ").exist? # => false Pathname("parent/child/grandchild").mkpath Pathname("parent/child/grandchild").exist? # => true Pathname("parent").rmtree Pathname("parent").exist? # => false
We can use a Pathname to open a file for reading or writing. This works just like the
require "pathname" Pathname("test").open("w") do |f| f.write "Hello, world" end Pathname("test").open do |f| f.read # => "Hello, world" end
Another cool thing about Pathname is that it's also compatible with
Kernel#open. So if we have some existing code that uses
Kernel#open, we can replace our strings with
Pathname objects and it will still work.
require "pathname" path = Pathname("test") f = open(path) f.read # => "Hello, world"
It can be convenient to process whole directories full of files as pathnames. To accomodate this usage,
Pathname provides the
.glob method. We can get an array of pathnames by saying
Pathname.glob and passing a file glob pattern.
require "pathname" Pathname.glob("*") # => [#<Pathname:LINK_TO_FOO> # #<Pathname:xmptmp-in4834I6J.rb>, # #<Pathname:test>, # #<Pathname:xmptmp-out4834VEQ.rb>, # #<Pathname:193-pathname.org>, # #<Pathname:FOO>]
This can make for some very concise code. Let's make some directories. Then we'll select all the subdirectories in the current directory.
require "pathname" %W[dir1 dir2 dir3].each do |d| Pathname(d).mkpath end Pathname.glob("*").select(&:directory?) # => [#<Pathname:dir2>, #<Pathname:dir1>, #<Pathname:dir3>]
So far I've shown just a few highlights of what can be found in the
pathname library. There are many more methods I haven't covered. I definitely recommend looking up the documentation and learning about what else it provides. If you have a program which does more than a little work with files and directories,
Pathname is definitely the way to go. Happy hacking!