Class: File::Find

Inherits:
Object
  • Object
show all
Defined in:
lib/file/find.rb

Constant Summary collapse

VERSION =

The version of the file-find library

'0.3.8'
VALID_OPTIONS =

:stopdoc:

%w[
  atime
  ctime
  follow
  ftype
  inum
  group
  links
  maxdepth
  mindepth
  mount
  mtime
  name
  pattern
  path
  perm
  prune
  size
  user
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Find

Creates and returns a new File::Find object. The options set for this object serve as the rules for determining what files the File::Find#find method will search for.

In addition to the standard list of valid options, you may also use FileTest methods as options, setting their value to true or false.

Example:

rule = File::Find.new(
   :name      => "*.rb",
   :follow    => false,
   :path      => ['/usr/local/lib', '/opt/local/lib'],
   :readable? => true
)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/file/find.rb', line 163

def initialize(options = {})
  @options = options

  @atime  = nil
  @ctime  = nil
  @ftype  = nil
  @group  = nil
  @follow = true
  @inum   = nil
  @links  = nil
  @mount  = nil
  @mtime  = nil
  @perm   = nil
  @prune  = nil
  @size   = nil
  @user   = nil

  @previous = nil
  @maxdepth = nil
  @mindepth = nil
  @filetest = []

  validate_and_set_options(options) unless options.empty?

  @filesystem = File.stat(@mount).dev if @mount

  @path ||= Dir.pwd
  @name ||= '*'
end

Instance Attribute Details

#atimeObject

Limits searches by file access time, where the value you supply is the number of days back from the time that the File::Find#find method was called.



51
52
53
# File 'lib/file/find.rb', line 51

def atime
  @atime
end

#ctimeObject

Limits searches by file change time, where the value you supply is the number of days back from the time that the File::Find#find method was called.



57
58
59
# File 'lib/file/find.rb', line 57

def ctime
  @ctime
end

#filetestObject

An array of two element arrays for storing FileTest methods and their boolean value.



67
68
69
# File 'lib/file/find.rb', line 67

def filetest
  @filetest
end

#followObject

Controls the behavior of how symlinks are followed. If set to true (the default), then follows the file pointed to. If false, it considers the symlink itself.



73
74
75
# File 'lib/file/find.rb', line 73

def follow
  @follow
end

#ftypeObject

Limits searches to specific types of files. The possible values here are those returned by the File.ftype method.



78
79
80
# File 'lib/file/find.rb', line 78

def ftype
  @ftype
end

#groupObject

Limits searches to files that belong to a specific group, where the group can be either a group name or ID.



62
63
64
# File 'lib/file/find.rb', line 62

def group
  @group
end

#inumObject

Limits search to a file with a specific inode number.



82
83
84
# File 'lib/file/find.rb', line 82

def inum
  @inum
end

Limits search to files with the specified number of links.



86
87
88
# File 'lib/file/find.rb', line 86

def links
  @links
end

#maxdepthObject

Limits search to a maximum depth into the tree relative to the starting search directory.



91
92
93
# File 'lib/file/find.rb', line 91

def maxdepth
  @maxdepth
end

#mindepthObject

Limits searches to a minimum depth into the tree relative to the starting search directory.



96
97
98
# File 'lib/file/find.rb', line 96

def mindepth
  @mindepth
end

#mountObject

Limits searches to the same filesystem as the specified directory. For Windows users, this refers to the volume.



101
102
103
# File 'lib/file/find.rb', line 101

def mount
  @mount
end

#mtimeObject

Limits searches by file modification time, where the value you supply is the number of days back from the time that the File::Find#find method was called.



107
108
109
# File 'lib/file/find.rb', line 107

def mtime
  @mtime
end

#nameObject Also known as: pattern

The name pattern used to limit file searches. The patterns that are legal for Dir.glob are legal here. The default is ‘*’, i.e. everything.



112
113
114
# File 'lib/file/find.rb', line 112

def name
  @name
end

#optionsObject

The list of options passed to the constructor and/or used by the File::Find#find method.



45
46
47
# File 'lib/file/find.rb', line 45

def options
  @options
end

#pathObject

The starting path(s) for the search. The default is the current directory. This can be a single path or an array of paths.



40
41
42
# File 'lib/file/find.rb', line 40

def path
  @path
end

#permObject

Limits searches to files which have permissions that match the octal value that you provide. For purposes of this comparison, only the user, group, and world settings are used.

You may optionally use symbolic permissions, e.g. “g+rw”, “u=rwx”, etc.

MS Windows only recognizes two modes, 0644 and 0444.



122
123
124
# File 'lib/file/find.rb', line 122

def perm
  @perm
end

#previousObject (readonly)

The file that matched previously in the current search.



142
143
144
# File 'lib/file/find.rb', line 142

def previous
  @previous
end

#pruneObject

Skips files or directories that match the string provided as an argument.



126
127
128
# File 'lib/file/find.rb', line 126

def prune
  @prune
end

#sizeObject

If the value passed is an integer, this option limits searches to files that match the size, in bytes, exactly. If a string is passed, you can use the standard comparable operators to match files, e.g. “>= 200” would limit searches to files greater than or equal to 200 bytes.



133
134
135
# File 'lib/file/find.rb', line 133

def size
  @size
end

#userObject

Limits searches to files that belong to a specific user, where the user can be either a user name or an ID.



138
139
140
# File 'lib/file/find.rb', line 138

def user
  @user
end

Instance Method Details

#findObject

Executes the find based on the rules you set for the File::Find object. In block form, yields each file in turn that matches the specified rules. In non-block form it will return an array of matches instead.

Example:

rule = File::Find.new(
   :name    => "*.rb",
   :follow  => false,
   :path    => ['/usr/local/lib', '/opt/local/lib']
)

rule.find{ |f|
   puts f
}


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/file/find.rb', line 209

def find
  results = [] unless block_given?
  paths   = @path.is_a?(String) ? [@path] : @path # Ruby 1.9.x compatibility

  if @prune
    prune_regex = Regexp.new(@prune)
  else
    prune_regex = nil
  end

  paths.each{ |path|
    begin
      Dir.foreach(path){ |file|
        next if file == '.'
        next if file == '..'

        if prune_regex
          next if prune_regex.match(file)
        end

        file = File.join(path, file)

        stat_method = @follow ? :stat : :lstat
        # Skip files we cannot access, stale links, etc.
        begin
          stat_info = File.send(stat_method, file)
        rescue Errno::ENOENT, Errno::EACCES
          next
        rescue Errno::ELOOP
          stat_method = :lstat # Handle recursive symlinks
          retry if stat_method.to_s != 'lstat'
        end

        # We need to escape any brackets in the directory name.
        glob = File.join(File.dirname(file).gsub(/([\[\]])/,'\\\\\1'), @name)

        # Dir[] doesn't like backslashes
        if File::ALT_SEPARATOR
          file.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
          glob.tr!(File::ALT_SEPARATOR, File::SEPARATOR)
        end

        if @mount
          next unless stat_info.dev == @filesystem
        end

        if @links
          next unless stat_info.nlink == @links
        end

        if @maxdepth || @mindepth
          file_depth = file.split(File::SEPARATOR).length
          path_depth = @path.split(File::SEPARATOR).length
          depth = file_depth - path_depth

          if @maxdepth && (depth > @maxdepth)
            if File.directory?(file)
              unless paths.include?(file) && depth > @maxdepth
                paths << file
              end
            end

            next
          end

          if @mindepth && (depth < @mindepth)
            if File.directory?(file)
              unless paths.include?(file) && depth < @mindepth
                paths << file
              end
            end

            next
          end
        end

        # Add directories back onto the list of paths to search unless
        # they've already been added
        #
        if stat_info.directory?
          paths << file unless paths.include?(file)
        end

        next unless Dir[glob].include?(file)

        unless @filetest.empty?
          file_test = true

          @filetest.each{ |array|
            meth = array[0]
            bool = array[1]

            unless File.send(meth, file) == bool
              file_test = false
              break
            end
          }

          next unless file_test
        end

        if @atime || @ctime || @mtime
          date1 = Date.parse(Time.now.to_s)

          if @atime
            date2 = Date.parse(stat_info.atime.to_s)
            next unless (date1 - date2).numerator == @atime
          end

          if @ctime
            date2 = Date.parse(stat_info.ctime.to_s)
            next unless (date1 - date2).numerator == @ctime
          end

          if @mtime
            date2 = Date.parse(stat_info.mtime.to_s)
            next unless (date1 - date2).numerator == @mtime
          end
        end

        if @ftype
          next unless File.ftype(file) == @ftype
        end

        if @group
          if @group.is_a?(String)
            if File::ALT_SEPARATOR
              begin
                next unless Sys::Admin.get_group(stat_info.gid, :LocalAccount => true).name == @group
              rescue Sys::Admin::Error
                next
              end
            else
              begin
                next unless Sys::Admin.get_group(stat_info.gid).name == @group
              rescue Sys::Admin::Error
                next
              end
            end
          else
            next unless stat_info.gid == @group
          end
        end

        if @inum
          next unless stat_info.ino == @inum
        end

        # Note that only 0644 and 0444 are supported on MS Windows.
        if @perm
          if @perm.is_a?(String)
            octal_perm = sym2oct(@perm)
            next unless stat_info.mode & octal_perm == octal_perm
          else
            next unless sprintf("%o", stat_info.mode & 07777) == sprintf("%o", @perm)
          end
        end

        # Allow plain numbers, or strings for comparison operators.
        if @size
          if @size.is_a?(String)
            regex = /^([><=]+)\s*?(\d+)$/
            match = regex.match(@size)

            if match.nil? || match.captures.include?(nil)
              raise ArgumentError, "invalid size string: '#{@size}'"
            end

            operator = match.captures.first.strip
            number   = match.captures.last.strip.to_i

            next unless stat_info.size.send(operator, number)
          else
            next unless stat_info.size == @size
          end
        end

        if @user
          if @user.is_a?(String)
            if File::ALT_SEPARATOR
              begin
                next unless Sys::Admin.get_user(stat_info.uid, :LocalAccount => true).name == @user
              rescue Sys::Admin::Error
                next
              end
            else
              begin
                next unless Sys::Admin.get_user(stat_info.uid).name == @user
              rescue Sys::Admin::Error
                next
              end
            end
          else
            next unless stat_info.uid == @user
          end
        end

        if block_given?
          yield file
        else
          results << file
        end

        @previous = file unless @previous == file
      }
    rescue Errno::EACCES
      next # Skip inaccessible directories
    end
  }

  block_given? ? nil : results
end