How to Traverse Symlinked Directories in Ruby with a "**" Glob

Can I traverse symlinked directories in Ruby with a ** glob?

Jonathan's clever and cunning approach is great, capable of slashing through hordes of symlinks with but a mere flick of a few asterisks, muahaha. However, it has the unfortunate side-effect of not returning immediate-child matches. An improved version might be:

Dir.glob("**{,/*/**}/*.rb")

Which will (in my tests) do both follow one symlink and return immediate children.

ZSH how to glob non-symlink files inside symlink directories

To follow symbolic links, you need yet another asterisk:

***/*.log

Look for the Recursive Globbing section in the zshexpn man page.

Depending on how your links are set up, you could get the same file listed multiple times in the glob results (that's probably why zsh doesn't include symbolic links in the ** pattern).

Confusion about Dir[] and File.join() in Ruby

File.join() simply concats all its arguments with separate slash.
For instance,

File.join("a", "b", "c")

returns "a/b/c". It is alsmost equivalent to more frequently used Array's join method, just like this:

["hello", "ruby", "world"].join(", ")
# => "hello, ruby, world"

Using File.join(), however, additionaly does two things: it clarifies that you are getting something related to file paths, and adds '/' as argument (instead of ", " in my Array example). Since Ruby is all about aliases that better describe your intentions, this method better suits the task.

Dir[] method accepts string or array of such strings as a simple search pattern, with "*" as all files or directories, and "**" as directories within other directories. For instance,

Dir["/var/*"]
# => ["/var/lock", "/var/backups", "/var/lib", "/var/tmp", "/var/opt", "/var/local", "/var/run", "/var/spool", "/var/log", "/var/cache", "/var/mail"]

and

Dir["/var/**/*"]
# => ["/var/lock", "/var/backups", "/var/backups/dpkg.status.3.gz", "/var/backups/passwd.bak" ... (all files in all dirs in '/var')]

It is a common and very convinient way to list or traverse directories recursively

Difference between pattern syntax in RSpec::Core::RakeTask and Pathname/Dir.glob

How glob patterns are used in RSpec::Core::RakeTask

RSpec::Core::RakeTask just runs the rspec executable. Setting a task's pattern just passes the pattern with the --pattern flag.

RSpec first constructs a list of paths in which to look for specs. If no files or directories are given on the command line, a single default path is used, 'spec'. RSpec looks for the pattern in each path. If the pattern begins with the path, RSpec just globs the pattern. If the pattern doesn't begin with the path, RSpec prepends the path to the pattern. So:

  • The pattern **/spec/**/*_spec.rb begins with a wildcard, so RSpec prepends the path, and spec/**/spec/**/*_spec.rb doesn't match anything.
  • The pattern spec/**/*_spec.rb begins with the path, so RSpec just uses it and it works.

The pattern **/*_spec.rb would also work; RSpec would prepend spec/.

RSpec::Core::RakeTask's default pattern

This part of the pattern **{,/*/**} allows it to follow symlinks. I don't know why RSpec::Core::RakeTask follows symlinks by default and command-line rspec doesn't.

I believe the the rake task's default pattern only works because RSpec recognizes that it begins with the path and doesn't prepend the path. I think if it were **{,/*/**}/*_spec.rb it would be clearer and would work in more situations.

Check if there is any file or directory matching pattern using ruby


Ruby-only

You could use Find, find and find :D.

I couldn't find any other File/Dir method that returns an Enumerator.

require 'find'
Find.find("/var/data/").find{|f| f=~/\.xml$/i }
#=> first xml file found inside "/var/data". nil otherwise
# or
Find.find("/var/data/").find{|f| File.extname(f).downcase == ".xml" }

If you really just want a boolean :

require 'find'
Find.find("/var/data/").any?{|f| f=~/\.xml$/i }

Note that if "/var/data/" exists but there is no .xml file inside it, this method will be at least as slow as Dir.glob.

As far as I can tell :

Dir.glob("/var/data/**/*.xml"){|f| break f}

creates a complete array first before returning its first element.

Bash-only

For a bash-only solution, you could use :

  • compgen
  • Shell find


Related Topics



Leave a reply



Submit