Video transcript & code
Today I want to go over some quick tips for working with filenames. Some of this may be familiar to you already, but if so there's still a tip at the end you might not have seen before.
To get the directory portion of a file path, we can use
File.dirname("/home/avdi/Dropbox/rubytapas/192-filenames.org") # => "/home/avdi/Dropbox/rubytapas"
This might seem like something we could accomplish just as easily with a String search, but
dirname is preferable for a few reasons. For one thing, it will use the correct directory separator for whatever platform it is running on. And for another, it has intelligent handling of relative paths. For instance, when we give it the name of a file in the current directory, it returns a dot instead of a blank string.
File.dirname("192-filenames.org") # => "."
It's important to realize, however, that
.dirname and its related methods do not actually test for the existence of a file.
File.dirname("DOESNOTEXIST") # => "."
The opposite of
.basename. It extracts just the file portion of a path, without any parent directories.
File.basename("/home/avdi/Dropbox/rubytapas/192-filenames.org") # => "192-filenames.org"
Given a second argument, it will also remove the provided extension from the result.
File.basename("/home/avdi/Dropbox/rubytapas/192-filenames.org", ".org") # => "192-filenames"
And then there is
File.extname, which unsurprisingly pulls out just the file extension of the given path.
File.extname("/home/avdi/Dropbox/rubytapas/192-filenames.org") # => ".org"
Again, this method has some intelligence over and above a simple string regex match. If we pass it a dotfile, such as
.profile, it will return the blank string.
File.extname("/home/avdi/.profile") # => ""
If you've ever worked with filenames in Ruby, everything we've just seen is probably very familiar to you. But there's one application of these methods that I used to think was overly complicated. Let's say we want to get just the basename of a file, sans extension, regardless of what that extension might be.
We might use a combination of
file = "/home/avdi/Dropbox/rubytapas/192-filenames.org" File.basename(file, File.extname(file)) # => "192-filenames"
But that seems overly cumbersome. As it turns out, there is an easier way, it just isn't that well documented. If we specify the special extension
.*, it will remove any extension.
(Note that it's just
.* that's supported, not any old file glob expression.)
File.basename("/home/avdi/Dropbox/rubytapas/192-filenames.org", ".*") # => "192-filenames" File.basename("/home/avdi/Dropbox/rubytapas/192-filenames.veg", ".*") # => "192-filenames"
I was pretty thrilled when I finally discovered that this was possible. I hope it helps you as well.
By the way, I've submitted a pull request to Ruby to better document the
#basename method. Now that Ruby is on Github it's very easy to submit this kind of documentation fix. If you ever find an under-documented corner case like this one, I definitely recommend submitting a PR for it. It's a great way to get started contributing to Ruby.