ぎょーぼのぶろぐ

IT系の話を書いていくブログです。今はRubyの勉強中。

【Ruby2.6】__FILE__ を使ってできること

前置き

前回の続きです。 __FILE__ を使って色々な情報を取得してみます。

実行している「ファイル名のみ」を取得する

最後のパス区切り文字以降がファイル名になるので、それを取り出すメソッドを使う、正規表現で抽出したり、文字列加工したりすることで取得することができます。

下記、取り出し例です。C:\gyobo\test\a.rb 内で実行すると全て、"a.rb"文字列を返します。__FILE__の中身が絶対パスか、相対パスかにかかわらず、同じ結果が返ります。

# File.basename を使う。
File.basename(__FILE__)            

# "/" で配列に分割後、配列要素の最後のものを取る。
__FILE__.split("/")[-1]

# 最後の"/"から最後までを抽出。先頭に"/" が残るので、それを削除する。 
# "/" が無い場合は、そのままの値を返す。
__FILE__.include?("/") ? __FILE__.match(/\/[^\/]*$/).to_s.delete("/") : __FILE__

素直に File.basename を使うのが一番良いと思います。何かの理由でFileオブジェクトが使えない場合は、split を使うのが次善策でしょうか。

実行しているファイルの絶対パスを取得する

前回の記事でも書きましたが、__FILE__相対パスが入る場合、相対パスのベースとなるディレクトリの情報は入っていません。ですので、カレントディレクトリの情報と合わせる必要があります。

絶対パスを自力で生成しようとする場合、Dir.pwd 等でカレントディレクトリを取得し、join で結合することになりますが、__FILE__絶対パスかどうかの確認が必要になり、また__FILE__ の中に、親ディレクト.. やカレントディレクト. 等が入っている場合、ちゃんと処理をしないとそのまま入ってきてしまいます。

File.expand_path を使えば、第一引数が絶対パスであれば、第二引数を指定しなければ、カレントディレクトリを基準に絶対パスへの展開を行ってくれます。また、... の処理も合わせて行ってくれるので、素直に File.expand_path を使った方が良いです。

下記、そのまんまですが、取り出し例です。ファイルの絶対パスは、C:\gyobo\test\a.rb です。

File.expand_path(__FILE__)      # => "C:\gyobo\test\a.rb"

注意点ですが、絶対パスを取得する場合は、取得前にカレントディレクトリの変更(Dir.chdirなど)をしてしまうと、正しく取得できません。

p __FILE__                      # => "a.rb"
File.expand_path(__FILE__)      # => "C:\gyobo\test\a.rb"
Dir.chdir("C:\\")
File.expand_path(__FILE__)      # => "C:\a.rb"      移動したカレントディレクトリからのパスを生成してしまう。

実行しているファイルがあるディレクトリを取得する。

実行しているファイルの絶対パスを取得した後、絶対パスディレクトリ部分を取得すればいいです。ですが、File.expand_pathだけを使う方法もあります。

# ファイルの絶対パスからディレクトリを取得する。
File.dirname(File.expand_path(__FILE__))      # => "C:\gyobo\test"

# File.expand_path だけを使っても取れる。第一引数だけ注意。
p File.expand_path("..", __FILE__)         # => "C:\gyobo\test"

File.expand_path の方は、なんで第一引数が親ディレクト..指定なのか?これは、File.expand_path の特性を利用しています。

File.expand_path メソッドは、「第一引数のパスを、第二引数のパスを基準にして絶対パスに展開」します。第二引数も相対パスの場合、カレントディレクトリを基準にして、第二引数、第一引数のパスを絶対パスに展開してくれます。

  • 第二引数に__FILE__を指定した場合、まず第二引数がカレントディレクトリを基準にして展開され、C:\gyobo\test\a.rb となります。
  • ですが、第二引数にはディレクトリが来ることが想定されているので、このパスは C:\gyobo\test\a.rb\ 、つまりa.rbはファイルではなくディレクトリである、と解釈されます。
  • ですので、a.rbディレクトリを基準にして一つ上のディレクトリが、ファイルのあるディレクトリとなるので、第一引数が親ディレクト..の指定となります。

この特性を利用すると、実行しているファイルと同じディレクトリにあるlibディレクトリのパスを取得したい場合は、下記のようになります。

# 実行ファイルと同じディレクトリにある"lib"ディレクトリの絶対パスを取得する。
p File.expand_path("..\lib", __FILE__)         # => "C:\gyobo\test\lib"

便利なのですが、見た目上、一階層ずれるので、一瞬間違っているように見えることが注意点です。また、カレントディレクトリを基準にして取得するので、前項と同様に、取得前にカレントディレクトリの変更(Dir.chdirなど)をしてしまうと、正しく取得できなくなります。

もう一つの方法として、__dir__ メソッドでも取得することができます。(ruby 2.0 以上?)

p __dir__           # => "C:\gyobo\test\"

こちらは、カレントディレクトリの変更をしても、元のディレクトリが正しく取得できるので、バージョンに問題がなければ、こちらを使う方がおすすめだと思います。

ただ、こちらも注意点があり、リファレンスマニュアルの__dir__ の項目 にもある通り、シンボリックリンクを解決」したパスを返してきます。ですので、パスの中にシンボリックリンクが含まれている場合、File.expand_path__dir__ の結果が違ってきます。

例えば、実体ファイルが C:\gyobo\test\a.rb で、C:\gyobo\test2\link_testC:\gyobo\test へのシンボリックリンクとして作成して、C:\gyobo\test2\link_test\ をカレントディレクトリにして、a.rb を実行します。

p File.expand_path("..", __FILE__)       # => "C:\gyobo\test2\link_test"
p __dir__                                # => "C:\gyobo\test"

これは、実行ファイルa.rb自体をシンボリックリンクにした場合も同様です。

Windows環境の場合は、そもそもシンボリックリンクを作ること自体がけっこう特殊な状況なので、問題はより起きにくいと思いますが、linux系で、シンボリックリンク生成をする可能性がある場合は、要注意です。

まとめ

  • 実行している「ファイル名のみ」を取得する
    • File.basename(__FILE__)
  • 実行しているファイルの絶対パスを取得する
    • File.expand_path(__FILE__)
  • 実行しているファイルのあるディレクトリの絶対パスを取得する
    • File.expand_path("..", __FILE__)
    • __dir__

注意点としては、2点です。