ファイル名を拡張子とそうでない部分(名前ないのかな?)とに分離したいとき、Rubyなら
file_name = "test.txt"
name = File.basename(file_name, ".*") #=> "test"
ext = File.extname(file_name) #=> ".txt"
とできるわけですが、なんとなくDRYじゃない感じがして、一行で書いてみたので残しておきます。
file_name = "test.txt"
name, ext = /\A(.+?)((?:\.[^.]+)?)\z/.match(file_name, &:captures) #=> ["test", ".txt"]
正規表現の最初の括弧でname
の部分をキャプチャします。
普通に(.+)
だとgreedyにマッチするので、拡張子部分までマッチしてしまって上手くいきません。
また(.*?)
だと、filename = ".bashrc"
のとき、[name, ext] == ["", ".bashrc"]
になってしまいます(上のbasename
とextname
の例ではそうなりません)。
次の括弧で.とそれに続く.を含まない文字列をキャプチャします。[^.]
としたのは、file_name = "test.1.txt"
のようなときにtestと.1.txtに分かれてしまわないようにです。
また、(\.[^.])?
でキャプチャしなかったのは、拡張子なしだった場合にextがnilではなく空文字列になるようにして、File.extname
と同じ挙動にしたかったからです。
Regexp#match
にブロックを渡すと、マッチが成功した時だけMatchDataオブジェクトを引数にブロックを実行してくれます。
なので、引数に&:captures
を渡すとキャプチャした部分が配列で返ってきて変数に代入できるというわけです。
ちなみにregexp.match(file_name).captures
でも良さそうにも見えますが、マッチに失敗した時にnil.captures
になってNoMethodErrorになります。