Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

recursive file list in Ruby

I'm new to Ruby (being a Java dev) and trying to implement a method (oh, sorry, a function) that would retrieve and yield all files in the subdirectories recursively.

I've implemented it as:

def file_list_recurse(dir)
  Dir.foreach(dir) do |f|
    next if f == '.' or f == '..'
    f = dir + '/' + f
    if File.directory? f
      file_list_recurse(File.absolute_path f) { |x| yield x }
    else
      file = File.new(f)
      yield file
    end
  end
end

My questions are:

  1. Does File.new really OPEN a file? In Java new File("xxx") doesn't... If I need to yield some structure that I could query file info (ctime, size etc) from what would it be in Ruby?
  2. { |x| yield x } looks a little strange to me, is this OK to do yields from recursive functions like that, or is there some way to avoid it?
  3. Is there any way to avoid checking for '.' and '..' on each iteration?
  4. Is there a better way to implement this?

Thanks

PS: the sample usage of my method is something like this:

curr_file = nil

file_list_recurse('.') do |file|
  curr_file = file if curr_file == nil or curr_file.ctime > file.ctime
end

puts curr_file.to_path + ' ' + curr_file.ctime.to_s

(that would get you the oldest file from the tree)

==========

So, thanks to @buruzaemon I found out the great Dir.glob function which saved me a couple of lines of code. Also, thanks to @Casper I found out the File.stat method, which made my function run two times faster than with File.new

In the end my code is looking something like this:

i=0
curr_file = nil

Dir.glob('**/*', File::FNM_DOTMATCH) do |f|
  file = File.stat(f)
  next unless file.file?
  i += 1
  curr_file = [f, file] if curr_file == nil or curr_file[1].ctime > file.ctime
end

puts curr_file[0] + ' ' + curr_file[1].ctime.to_s
puts "total files #{i}"

=====

By default Dir.glob ignores file names starting with a dot (considered to be 'hidden' in *nix), so it's very important to add the second argument File::FNM_DOTMATCH

like image 214
Oleg Mikheev Avatar asked Nov 02 '11 16:11

Oleg Mikheev


People also ask

How do I list all files recursively?

Try any one of the following commands to see recursive directory listing: ls -R : Use the ls command to get recursive directory listing on Linux. find /dir/ -print : Run the find command to see recursive directory listing in Linux. du -a . : Execute the du command to view recursive directory listing on Unix.

What are recursive files?

Alternatively referred to as recursive, recurse is a term used to describe the procedure capable of being repeated. For example, when listing files in a Windows command prompt, you can use the dir /s command to recursively list all files in the current directory and any subdirectories.

How do I search for a recursive folder?

An easy way to do this is to use find | egrep string . If there are too many hits, then use the -type d flag for find. Run the command at the start of the directory tree you want to search, or you will have to supply the directory as an argument to find as well. Another way to do this is to use ls -laR | egrep ^d .

Is the option to list sub directories recursively?

If you name one or more directories on the command line, ls will list each one. The -R (uppercase R) option lists all subdirectories, recursively.


3 Answers

How about this?

puts Dir['**/*.*']
like image 91
buruzaemon Avatar answered Oct 01 '22 11:10

buruzaemon


According to the docs File.new does open the file. You might want to use File.stat instead, which gathers file-related stats into a queryable object. But note that the stats are gathered at point of creation. Not when you call the query methods like ctime.

Example:

Dir['**/*'].select { |f| File.file?(f) }.map { |f| File.stat(f) }
like image 30
Casper Avatar answered Oct 01 '22 11:10

Casper


this thing tells me to consider accepting an answer, I hope it wouldn't mind me answering it myself:

i=0
curr_file = nil

Dir.glob('**/*', File::FNM_DOTMATCH) do |f|
  file = File.stat(f)
  next unless file.file?
  i += 1
  curr_file = [f, file] if curr_file == nil or curr_file[1].ctime > file.ctime
end

puts curr_file[0] + ' ' + curr_file[1].ctime.to_s
puts "total files #{i}"
like image 35
Oleg Mikheev Avatar answered Oct 01 '22 12:10

Oleg Mikheev