Filename Globbing

Filename globbing is a pattern-matching feature implemented in certain Ruby methods.

Filename-globbing methods find filesystem entries (files and directories) that match certain patterns; these methods are:

These methods are quite different from filename-matching methods (not discussed here), which match patterns against string paths, and do not access the filesystem; those methods are:

These are the basic elements of filename-globbing patterns; see the sections below for details:

Pattern Meaning Examples
Simple string. Matches itself. 'LEGAL'
'*' Matches any sequence of characters. '*.txt'
'?' Matches any single character. '?.txt'
'[abc]',
'[^abc]'
Matches a single character from a set. 'x[abc]y',
'x[^abc]y'
'[a-z]‘,
'[^a-z]'
Matches a single character from a range. 'x[0-9]y',
'x[^0-9]y'
'{ , }' Matches alternatives. '{abc,def}'
'**' Matches directories recursively. '**/test.rb'
'\' Escapes the next character. '\*', '\?'

Patterns

Simple String

A “simple string” is one that does not contain special filename-globbing patterns; see the table above.

A simple string matches itself:

Dir.glob('LEGAL') # => ["LEGAL"]
Dir.glob('LEGA')  # => []  # Must be exact.
Dir.glob('legal') # => []  # Case-sensitive.

Note that case-sensitivity may not be modified by flags.

By default, the Windows short name pattern is disabled:

Dir.glob('PROGRAM~1') # => []

It may be enabled by flag File::FNM_SHORTNAME.

Any Sequence of Characters ('*')

The asterisk pattern ('*') matches any sequence of characters:

Dir.glob('*').take(3) # => ["BSDL", "CONTRIBUTING.md", "COPYING"]
Dir.glob('\*')        # => []  # Escaped.

By default, the asterisk pattern does not match a leading period (as in a dot-file):

Dir.glob('*').select {|entry| entry.start_with?('.') } # => []

That matching may be enabled by flag File::FNM_DOTMATCH.

The asterisk pattern does not match across file separators:

Dir.glob('*.rb').select {|entry| entry.include?('/') } # => []

Therefore flag File::FNM_PATHNAME does not affect the pattern.

Single Character ('?')

The question-mark pattern ('?') matches any single character:

Dir.glob('???') # => ["GPL", "bin", "doc", "enc", "ext", "jit", "lib", "man"]
Dir.glob('??')  # => ["gc"]  # Only one entry with a 2-character name.
Dir.glob('?')   # => []      # No entries with a 1-character name.
Dir.glob('\?')  # => []      # No entries containing character '?'.

By default, the question-mark pattern does not match a leading period (as in a dot-file):

Dir.glob(".???") # => [".git"]
Dir.glob("????").select {|entry| entry.start_with?('.') } # => []

That matching may be enabled by flag File::FNM_DOTMATCH.

Single Character from a Set ('[abc]', '[^abc]')

Characters enclosed in square brackets define a set of characters, any of which matches a single character:

Dir.glob('[efgh][abcd]')  # => ["gc"]
Dir.glob('\[efgh][abcd]') # => []  # Escaped.

The character set may be negated:

Dir.glob('[^abcd][^efgh]') # => ["gc"]

Single Character from a Range ('[a-c]', '[^a-c]')

A range of characters enclosed in square brackets defines a set of characters, any of which matches a single character:

Dir.glob('[k-m][h-j][a-c]')  # => ["lib"]
Dir.glob('\[k-m][h-j][a-c]') # => []  # Escaped.

The range may be negated:

Dir.glob('[^k-m][h-j][a-c]')  # => []
Dir.glob('[^a-c][^k-m][^h-j]') # => ["GPL", "doc", "enc", "ext", "jit", "lib", "man"]

Alternatives ('{ , }')

The alternatives pattern consists of comma-separated strings enclosed in curly braces:

Dir.glob('{k,L,R}*')  # => ["kernel.rb", "LEGAL", "README.ja.md", "README.md"]
Dir.glob('{R,L,k}*')  # => ["README.ja.md", "README.md", "LEGAL", "kernel.rb"]
# Whitespace matters:
Dir.glob('{k ,L,R}*') # => ["LEGAL", "README.ja.md", "README.md"]

Recursive Directory Matching ('**')

The double-asterisk pattern ('**') matches directories recursively:

# Find all entries everywhere ending with '.ja'.
Dir.glob('**/*.ja')
# => ["COPYING.ja", "doc/pty/README.expect.ja", "doc/pty/README.ja"]

# Find all entries everywhere ending with '.rb'.
Dir.glob('**/*.rb').size    # => 7574
Dir.glob('**/*.rb').take(3)
# => ["KNOWNBUGS.rb", "array.rb", "ast.rb"]

# Find all entries in directory 'lib' ending with `.rb'.
Dir.glob('lib/**/*.rb').size # => 626
Dir.glob('lib/**/*.rb').take(3)
# # =>
# ["lib/English.rb",
#  "lib/bundled_gems.rb",
#  "lib/bundler/build_metadata.rb"]

# Find all entries in directory 'test/ruby' ending with '.rb'.
Dir.glob('test/ruby/**/*.rb').size # => 200
Dir.glob('test/ruby/**/*.rb').take(3)
# # =>
# ["test/ruby/allpairs.rb",
#  "test/ruby/beginmainend.rb",
#  "test/ruby/box/a.1_1_0.rb"]

# Escaped.
Dir.glob('\**/*.rb') # => []

Escape ('\')

The backslash character ('\') may be used to escape any of the characters that filename globbing treats as special:

Dir.glob('\*')               # => []
Dir.glob('\?')               # => []
Dir.glob('\[efgh][abcd]')    # => []
Dir.glob('\[k-m][h-j][a-c]') # => []
Dir.glob('\**/*.rb')         # => []

Keyword Arguments

Keyword Value Default Meaning
base String path. '.' Root for searching.
flags Logical OR of constants. 0 Modify globbing behavior.
sort true or false true Whether returned array is to be sorted.

base

Optional keyword argument base (defaults to '.') specifies where in the filesystem the searching is to begin:

Dir.glob('*').size                  # => 241
Dir.glob('*').take(3)
# => ["BSDL", "CONTRIBUTING.md", "COPYING"]

Dir.glob('*', base: 'lib').size     # => 72
Dir.glob('*', base: 'lib').take(3)
# => ["English.gemspec", "English.rb", "bundled_gems.rb"]

Dir.glob('*', base: 'lib/net').size # => 5
Dir.glob('*', base: 'lib/net').take(3)
# => ["http", "http.rb", "https.rb"]

flags

Optional keyword argument flags (defaults to 0) may be the bitwise OR of the constants File::FNM*:

Dir.glob('*', flags: File::FNM_DOTMATCH | File::FNM_NOESCAPE)

These are the constants for filename-globbing patterns; see the sections below for details:

Constant Meaning
File::FNM_DOTMATCH Make pattern '*' match a leading period.
File::FNM_NOESCAPE Disable escaping.
File::FNM_SHORTNAME Enable short-name matching (Windows only).

These constants do not affect filename globbing:

Constant File::FNM_DOTMATCH

By default, filename globbing does not allow patterns '*' and '?' to match a dotfile name (i.e, an entry name beginning with a dot); use constant File::FNM_DOTMATCH to enable the match:

Dir.glob('*').size                               # => 241
Dir.glob('*', flags: File::FNM_DOTMATCH).size    # => 256
Dir.glob('*', flags: File::FNM_DOTMATCH).take(3) # => [".", ".dir-locals.el", ".document"]

Constant File::FNM_NOESCAPE

By default filename globbing has escaping enabled; use constant File::FNM_NOESCAPE to disable it:

Dir.glob('*').size  # => 241
Dir.glob('\*').size # => 0

Constant File::FNM_SHORTNAME

By default, Windows shortname matching is disabled; use constant File::FNM_SHORTNAME to enable it (on Windows only).

Using that constant allows patterns to match short names in filename globbing on Windows, which can be useful for compatibility with legacy applications that rely on these short names; see 8.3 filename. This feature helps ensure that file operations work correctly even when dealing with files that have long names.

sort

Optional keyword argument sort (defaults to 'true') specifies whether the returned array is to be sorted:

Dir.glob('*').take(3)
# => ["BSDL", "CONTRIBUTING.md", "COPYING"]
Dir.glob('*', sort: false).take(3)
# => ["gc.rb", "yjit.rb", "iseq.h"]