How to make Ruby's Find.find follow symlinks?
Asked Answered
T

6

11

I have a file hierarchy and some of the sub-directories are relative symlinks. I am using Ruby's Find.find to crawl through these dirs and find some specific files. However it's not looking into any directory which is a symlink (it follows files which are symlinks).

Looking at the source code it seems the problem is because it's using File.lstat(file).directory? to test if something is a directory. This returns false for symlinks but File.stat.directory? returns true.

How can I make Find.find follow symlinks, short of monkey patching it to use File.stat instead of File.lstat?

Top answered 20/10, 2010 at 1:20 Comment(0)
T
0

For anyone else watching, I ended up using Pathname and the following recursive code:

def all_files_under(*paths)
  paths.flatten!
  paths.map! { |p| Pathname.new(p) }
  files = paths.select { |p| p.file? }
  (paths - files).each do |dir|
    files << all_files_under(dir.children)
  end
  files.flatten
end
Top answered 16/4, 2011 at 22:26 Comment(0)
K
6

I came across the similar situation and decided to follow the real path without extra gem.

require 'find'

paths = ARGV

search_dirs = paths.dup
found_files = Array.new

until search_dirs.size == 0
  Find.find( search_dirs.shift ) do |path|
    if File.directory?( path ) && File.symlink?( path )
      search_dirs << File.realdirpath( path )
    else
      found_files << path
    end
  end
end

puts found_files.join("\n")

This way can't keep the original path with symbolic link but is fine for me at the moment.

Kiersten answered 1/12, 2013 at 22:54 Comment(0)
F
3

Use the file-find library by Daniel J. Berger. It's available as a Ruby gem. Then you can find recursively with:

require 'rubygems'
require 'file/find'
File::Find.new(:follow => false).find { |p| puts p }

NB: contrary to the documentation and intuition, setting :follow => false will actually make File::Find follow all symlinks, at least on my machine (Ubuntu 10.04, Ruby 1.8.7, file-find 0.3.4).

There is a bunch of other options available for File::Find, like name pattern, file type, atime, ctime, mtime, etc. Take a look at the RDoc.

Ferminafermion answered 14/4, 2011 at 16:22 Comment(1)
Thanks. I ended up using <code>Pathname</code> with a recursive function to do this. I'll try this out next time I'm in that codebase (my question was from Oct'2010).Top
C
1

why not use Dir instead ? It follows symlinks Or you can try alib

To make Dir find files recursively, try double asterix Dir["**/*"]

Concussion answered 20/10, 2010 at 1:45 Comment(3)
How do I make Dir find files recursively? I don't see a method in the Dir class - am I missing something here?Top
While "alib" seems to have similar code, I have never heard of it - it's not on github/gemcutter and the name itself (is it really "a lib") doesn't inspire much confidence. Have you used it in production code? How was the experience?Top
Dir["*/"] seems to work only for one level of hierarchy - I had multiple levels, with some of them as symlinks. I ended up using Pathname#children (ruby-doc.org/core/classes/Pathname.html#M001721)Top
R
1

Wrote another option with loop checking and only limited recursion. Works with jruby as well.

Here's a gist: https://gist.github.com/akostadinov/05c2a976dc16ffee9cac

Remove answered 9/9, 2014 at 9:4 Comment(0)
T
0

For anyone else watching, I ended up using Pathname and the following recursive code:

def all_files_under(*paths)
  paths.flatten!
  paths.map! { |p| Pathname.new(p) }
  files = paths.select { |p| p.file? }
  (paths - files).each do |dir|
    files << all_files_under(dir.children)
  end
  files.flatten
end
Top answered 16/4, 2011 at 22:26 Comment(0)
R
0

Here is a simpler and more efficient version of the recursive Pathname method:

def find_files(*paths)
  paths.flatten.map do |path|
    path = Pathname.new(path)
    path.file? ? [path.to_s] : find_files(path.children)
  end.flatten
end 
Revisal answered 6/6, 2017 at 4:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.