RubyのFile.expand_path('相対パス', __FILE__)の意味

RailsなどのRubyライブラリのソースコードを見ていると、よく

File.expand_path('相対パス', __FILE__)

という一文を目にする。ちょっと調べてみた。

File.expand_pathとは

riコマンドで調べてみる

$ ri File.expand_path

(from ruby core)
------------------------------------------------------------------------------
  File.expand_path(file_name [, dir_string] )  ->  abs_file_name

------------------------------------------------------------------------------

Converts a pathname to an absolute pathname. Relative paths are referenced
from the current working directory of the process unless
dir_string is given, in which case it will be used as the
starting point. The given pathname may start with a ``~'', which expands to
the process owner's home directory (the environment variable HOME must be set
correctly). ``~user'' expands to the named user's home directory.

       File.expand_path("~oracle/bin")           #=> "/home/oracle/bin"
       File.expand_path("../../bin", "/tmp/x")   #=> "/bin"

相対パス絶対パスに変換した文字列を返す
・第2引数を指定しない場合「プロセスのカレントワーキングディレクトリ」を相対パスの基準にする
・第2引数を指定した場合「第2引数で指定したディレクトリ」を相対パスの基準にする

検証用ディレクトリ構造

検証用に、以下のディレクトリを作った。a.rbからlib/を参照することを想定して、検証する。

/
└── Users/
    └── hidenorimaehara/
        └── xxx/
            └── yyy/
                └── a.rb
                └── lib/

File.expand_path('./lib')

まずは第2引数を指定しないでやってみる。

a.rb

プロセスのカレントワーキングディレクトリを確認するため、Dir.pwdの結果も表示することにする。

puts Dir.pwd
puts File.expand_path('./lib')
yyyディレクトリで実行
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで実行
$ ruby ./yyy/a.rb
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/lib

プロセスのカレントワーキングディレクトリを相対パスの基準として絶対パスに変換していることが分かる。つまり、コマンドを実行するディレクトリによって結果が変わる。

File.expand_path('./lib', '/Users/hidenorimaehara/xxx/yyy/')

次に第2引数を与えてみる。yyyディレクトリの絶対パスを第2引数に指定してみる。

a.rb

さきほどのa.rbを以下のように変更。

puts Dir.pwd
puts File.expand_path('./lib', '/Users/hidenorimaehara/xxx/yyy/')
yyyディレクトリで実行
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで実行
$ ruby ./yyy/a.rb
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/lib

第2引数に指定したディレクトリを基準として相対パスを解析していることがわかる。

__FILE__

ここで一旦脇道にそれて、__FILE__について調べる。__FILE__は、現在のソースファイル名を返す変数。実際にどのような値を返しているのか調べてみる。

a.rb
puts __FILE__
yyyディレクトリで
$ ruby a.rb
a.rb
xxxディレクトリで
$ ruby ./yyy/a.rb
./yyy/a.rb

コマンドを実行したディレクトリからの相対パスでファイル名を返しているようだ。(こちらによると相対を返すか絶対を返すかは状況によって変わるらしい)

File.expand_path('./lib', __FILE__)

となるとFile.expand_pathの第2引数に__FILE__を指定すれば、現在のソースファイルを基点とできる。第1引数に'./lib'、第2引数に__FILE__を指定してみる。

a.rb
puts Dir.pwd
puts File.expand_path('./lib', __FILE__)
yyyディレクトリで
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/a.rb/lib
xxxディレクトリで
$ ruby ./yyy/a.rb 
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/a.rb/lib

ありゃ
./では、a.rb(ファイル名)自体もパスに入ってしまっている。

File.expand_path('../lib', __FILE__)

ということで、第1引数に../libにしてみる。

a.rb
puts Dir.pwd
puts File.expand_path('../lib', __FILE__)
yyyディレクトリで
$ ruby a.rb
/Users/hidenorimaehara/xxx/yyy
/Users/hidenorimaehara/xxx/yyy/lib
xxxディレクトリで
$ ruby ./yyy/a.rb 
/Users/hidenorimaehara/xxx
/Users/hidenorimaehara/xxx/yyy/lib

できた!

以下のように使う

こうすれば、プロセスのカレントワーキングディレクトリがどこであっても、相対的な位置関係を参照できる

# このソースファイルと同ディレクトリにあるb.rbをrequire
require File.expand_path('../b', __FILE__)

# このソースファイルと同ディレクトリにあるlibディレクトリをrequireのロードパスに追加
$: << File.expand_path('../lib', __FILE__)