Confusing behavior of File.dirname - ruby

I have written a couple of small Ruby scripts for system administration using Ruby 1.9.3. In one script I use:
File.dirname(__FILE__)
to get the directory of the script file. This returns a relative path, however when I call the script from a second script File.dirname returns an absolute path.
Ruby Doc lists an absolute return path in its example whereas I found a discussion on Ruby Forum where a user says dirname should only return a relative path.
I am using the suggested solution from Ruby Forums to use File.expand_path to always get the absolute path like this:
File.expand_path(File.dirname(__FILE__))
but is there a way to make the behaviour of dirname consistent?
UPDATE:
To expand on Janathan Cairs answer, I made two scripts:
s1.rb:
puts "External script __FILE__: #{File.dirname(__FILE__)}"
s0.rb:
puts "Local script __FILE__: #{File.dirname(__FILE__)}"
require './s1.rb'
Running ./s0.rb gives the following output:
Local script __FILE__: .
External script __FILE__: /home/kenneth/Pictures/wp/rip_vault

File.dirname should return an absolute path if given an absolute path, and a relative path if given a relative path:
File.dirname('/home/jon/test.rb') # => '/home/jon'
File.dirname('test.rb') # => '.'
__FILE__ returns the name of the current script, which is therefore a relative path from the current directory. That means you should always use expand_path if you want to get the absolute path with File.dirname(__FILE__).
NB Ruby 2.0.0 introduces the __dir__ constant

If you already upgraded to Ruby 2.0, you can use the new constant
__dir__
otherwise you can use
File.expand_path('..', __FILE__)
which is shorter than
File.expand_path(File.dirname(__FILE__))
File.expand_path documentation

Related

Ruby: How to find path relative to where class was instantiated?

I am creating a gem that is a Rack application, so I assume my application is going to be instantiated in a config.ru file. I expect certain paths to be relative to this config.ru file. So how can I get and set the path when the app is initialized?
For example:
Hidden away in my gem:
class MyApp
def initialize
#base_path = get_the_base_path_here
end
def call(env)
html = render_view(#base_path + '/views/index.erb')
end
end
User of the gem's config.ru:
require 'my_app'
run MyApp.new
...and their views directory:
/views
index.erb
Update:
One way to achieve this is to pass in the base path as an argument, but I would like to find a way to achieve this without passing it as an argument.
require 'my_app'
run MyApp.new(File.dirname(__FILE__))
Absolute Path of Current File
In general, you can simply use File.expand_path(__FILE__) to find the absolute path of the current file, which you can then store a variable or global if you like. For example:
$file_path = File.expand_path(__FILE__)
Absolute Path of Current Program
File.expand_path($0) is similar, but returns the program that was called. The distinction is sometimes subtle, but can be useful from time to time.
Creating an Absolute Path to a File in the Same Base Directory
If you want to use the directory name of the location of the current file to address another file, you can use File#join. For example:
File.join File.dirname(File.expand_path(__FILE__)), '.X11-unix'
=> "/tmp/.X11-unix"
Probably not the best way but you can find config.ru with:
$:.find{|path| File.exists? "#{path}/config.ru"}

How do I find the location of the gem?

I'm developing a library that provides access to gem metadata, including it's location on the file system. The idea was to let gem authors set it to a relative path from any script:
# $root/example.gemspec
Example::Gem.root '.' # => $root/
# $root/lib/example/gem.rb
Example::Gem.root '../..' # => $root/
Then, the path of the current script would be used to compute the absolute path. My implementation is currently as follows:
def root(relative_to = nil, file = __FILE__)
unless relative_to.nil?
#root = File.expand_path relative_to, File.dirname(file)
end
#root
end
I thought __FILE__ would return the path to the caller's script, but that assumption is wrong.
It worked within the library itself, but broke down when I tried to integrate it with one of my other gems; the generated path was always relative to the support library itself.
How can I implement this without having to pass the current __FILE__ on every call? Otherwise, there isn't much value to be gained; writing root('../..', __FILE__) is almost the same as writing an actual method to do the same thing.
If it's possible to figure out the path without having to specify anything, that would be even better, but I couldn't think of anything. How does Rails do it?
By the way, I'm aware of Gem::Specification#gem_dir, but it always returns paths relative to the installation directory, even if the gem is not actually there, which makes it useless in a development environment.
You can always make use of the backtrace facility provided:
caller.first
It produces an amalgam of file and line but is usually separated by :. I'd be careful to allow for filenames or paths that may contain colon for whatever reason by ignoring the line information but preserving the rest. In other words, do not split but sub:
caller.first.sub(/:\d+:in .*$/, '')

File.expand_path("../../Gemfile", __FILE__) How does this work? Where is the file?

ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
I'm just trying to access a .rb file from the some directory and a tutorial is telling me to use this code but I don't see how it is finding the gem file.
File.expand_path('../../Gemfile', __FILE__)
is a somewhat ugly Ruby idiom for getting the absolute path to a file when you know the path relative to the current file. Another way of writing it is this:
File.expand_path('../Gemfile', File.dirname(__FILE__))
both are ugly, but the first variant is shorter. The first variant is, however, also very non-intuitive until you get the hang of it. Why the extra ..? (but the second variant may give a clue as to why it is needed).
This is how it works: File.expand_path returns the absolute path of the first argument, relative to the second argument (which defaults to the current working directory). __FILE__ is the path to the file the code is in. Since the second argument in this case is a path to a file, and File.expand_path assumes a directory, we have to stick an extra .. in the path to get the path right. This is how it works:
File.expand_path is basically implemented like this (in the following code path will have the value of ../../Gemfile and relative_to will have the value of /path/to/file.rb):
def File.expand_path(path, relative_to=Dir.getwd)
# first the two arguments are concatenated, with the second argument first
absolute_path = File.join(relative_to, path)
while absolute_path.include?('..')
# remove the first occurrence of /<something>/..
absolute_path = absolute_path.sub(%r{/[^/]+/\.\.}, '')
end
absolute_path
end
(there's a little bit more to it, it expands ~ to the home directory and so on -- there are probably also some other issues with the code above)
Stepping through a call to the code above absolute_path will first get the value /path/to/file.rb/../../Gemfile, then for each round in the loop the first .. will be removed, along with the path component before it. First /file.rb/.. is removed, then on the next round /to/.. is removed, and we get /path/Gemfile.
To make a long story short, File.expand_path('../../Gemfile', __FILE__) is a trick to get the absolute path of a file when you know the path relative to the current file. The extra .. in the relative path is to eliminate the name of the file in __FILE__.
In Ruby 2.0 there is a Kernel function called __dir__ that is implemented as File.dirname(File.realpath(__FILE__)).
Two references:
File::expand_path method documentation
How does __FILE__ work in Ruby
I stumbled across this today:
boot.rb commit in the Rails Github
If you go up two directories from boot.rb in the directory tree:
/railties/lib/rails/generators/rails/app/templates
you see Gemfile, which leads me to believe that File.expand_path("../../Gemfile", __FILE__) references following file: /path/to/this/file/../../Gemfile

Can a Ruby script tell what directory it’s in?

Inspired by "Getting the source directory of a Bash script from within", what's the Ruby way to do this?
For newer versions of Ruby, try:
__dir__
For older versions of Ruby (< 2.0), the script being run can be found using:
File.dirname(__FILE__) - relative path; or
File.expand_path(File.dirname(__FILE__)) - the absolute path.
Note: Using __dir__ will return the script path even after a call to Dir.chdir; whereas, using the older syntax may not return the path to the script.
Use __dir__
As of Ruby 2.0, __dir__ is the simplest way to get this. It
Returns the canonicalized absolute path of the directory of the file
from which this method is called.
See the __dir__ documentation, and "Why is __FILE__ uppercase and __dir__ lowercase?".
use __dir__
File.dirname(__FILE__) is not a proper way to get directory where script is stored.
At start working directory and directory with script file is the same, but it may change.
For example:
Dir.chdir('..') do
puts __dir__
puts File.expand_path(File.dirname(__FILE__))
end
for script file stored in /Desktop/tmp running it will give output
/home/mateusz/Desktop/tmp
/home/mateusz/Desktop
ENV["PWD"] seems the simplest way for me under Linux. I don't know of an OS-agnostic way.

What does __FILE__ mean in Ruby?

I see this all the time in Ruby:
require File.dirname(__FILE__) + "/../../config/environment"
What does __FILE__ mean?
It is a reference to the current file name. In the file foo.rb, __FILE__ would be interpreted as "foo.rb".
Edit: Ruby 1.9.2 and 1.9.3 appear to behave a little differently from what Luke Bayes said in his comment. With these files:
# test.rb
puts __FILE__
require './dir2/test.rb'
# dir2/test.rb
puts __FILE__
Running ruby test.rb will output
test.rb
/full/path/to/dir2/test.rb
The value of __FILE__ is a relative path that is created and stored (but never updated) when your file is loaded. This means that if you have any calls to Dir.chdir anywhere else in your application, this path will expand incorrectly.
puts __FILE__
Dir.chdir '../../'
puts __FILE__
One workaround to this problem is to store the expanded value of __FILE__ outside of any application code. As long as your require statements are at the top of your definitions (or at least before any calls to Dir.chdir), this value will continue to be useful after changing directories.
$MY_FILE_PATH = File.expand_path(File.dirname(__FILE__))
# open class and do some stuff that changes directory
puts $MY_FILE_PATH
__FILE__ is the filename with extension of the file containing the code being executed.
In foo.rb, __FILE__ would be "foo.rb".
If foo.rb were in the dir /home/josh then File.dirname(__FILE__) would return /home/josh.
In Ruby, the Windows version anyways, I just checked and __FILE__ does not contain the full path to the file. Instead it contains the path to the file relative to where it's being executed from.
In PHP __FILE__ is the full path (which in my opinion is preferable). This is why, in order to make your paths portable in Ruby, you really need to use this:
File.expand_path(File.dirname(__FILE__) + "relative/path/to/file")
I should note that in Ruby 1.9.1 __FILE__ contains the full path to the file, the above description was for when I used Ruby 1.8.7.
In order to be compatible with both Ruby 1.8.7 and 1.9.1 (not sure about 1.9) you should require files by using the construct I showed above.

Resources