14

Is there a way to open a file case-insensitively in Ruby under Linux? For example, given the string foo.txt, can I open the file FOO.txt?

One possible way would be reading all the filenames in the directory and manually search the list for the required file, but I'm looking for a more direct method.

imgx64
  • 4,062
  • 5
  • 28
  • 44

2 Answers2

20

One approach would be to write a little method to build a case insensitive glob for a given filename:

def ci_glob(filename)
  glob = ''
  filename.each_char do |c|
    glob += c.downcase != c.upcase ? "[#{c.downcase}#{c.upcase}]" : c
  end
  glob
end

irb(main):024:0> ci_glob('foo.txt')
=> "[fF][oO][oO].[tT][xX][tT]"

and then you can do:

filename = Dir.glob(ci_glob('foo.txt')).first

Alternatively, you can write the directory search you suggested quite concisely. e.g.

filename = Dir.glob('*').find { |f| f.downcase == 'foo.txt' }

Prior to Ruby 3.1 it was possible to use the FNM_CASEFOLD option to make glob case insensitive e.g.

filename = Dir.glob('foo.txt', File::FNM_CASEFOLD).first
if filename
  # use filename here
else
  # no matching file
end

The documentation suggested FNM_CASEFOLD couldn't be used with glob but it did actually work in older Ruby versions. However, as mentioned by lildude in the comments, the behaviour has now been brought inline with the documentation and so this approach shouldn't be used.

mikej
  • 65,295
  • 17
  • 152
  • 131
  • 2
    For most of the `IO` and `File` methods, Ruby simply passes everything through to libc. So, I guess this might basically randomly work or not, depending on how your operating system's libc works. – Jörg W Mittag Sep 06 '10 at 14:53
  • @Jörg Thanks. I have updated the answer with a couple of other ideas that avoid the need to rely on `FNM_CASEFOLD`. – mikej Sep 06 '10 at 15:44
  • 1
    On my system, FNM_CASEFOLD is honoured in Ruby 2.0.0 but ignored in Ruby 2.2.0. Definitely not reliable. – Lupius Dec 02 '15 at 21:05
  • 1
    `FNM_CASEFOLD` is no longer honoured on 3.1 as of https://github.com/ruby/ruby/pull/4583 which brings the `Dir.glob` behaviour inline with what the docs have been saying for years https://docs.ruby-lang.org/en/3.1/File/File/Constants.html#FNM_CASEFOLD – lildude Nov 13 '22 at 14:13
  • Thanks @lildude - I've updated the answer to reflect this – mikej Nov 14 '22 at 09:37
1

You can use Dir.glob with the FNM_CASEFOLD flag to get a list of all filenames that match the given name except for case. You can then just use first on the resulting array to get any result back or use min_by to get the one that matches the case of the orignial most closely.

def find_file(f)
  Dir.glob(f, File::FNM_CASEFOLD).min_by do |f2|
    f.chars.zip(f2.chars).count {|c1,c2| c1 != c2}
  end
end

system "touch foo.bar"
system "touch Foo.Bar"
Dir.glob("FOO.BAR", File::FNM_CASEFOLD) #=> ["foo.bar", "Foo.Bar"]
find_file("FOO.BAR") #=> ["Foo.Bar"]
sepp2k
  • 363,768
  • 54
  • 674
  • 675