GoRegex

Globbing in Go

How to glob directories using regular expressions

Introduction

In the Go programming language, we can use the filepath.Match function to implement a glob function to find pattern matches in directories and file paths. For example, given the following directory structure:

/top
  /one
    template.txt
  /two
    template.txt
  /three
    template.txt
  /four
    /sub
      template.txt
    template.txt

If you wanted a list of file paths for the text files, we would need to iterate through the directory structure and select wildcards ending with a .txt extension.

Should no errors occur, we'd expect an output along the lines of:

[
  "/top/one/template.txt",
  "/top/two/template.txt",
  "/top/three/template.txt",
  "/top/four/sub/template.txt",
  "/top/four/template.txt"
]

Implementation

Let's break it down. We'll start by declaring the function and specifying its arguments and return values.

The function will have two arguments, a string containing the root directory we'd like to glob and a string containing the pattern we'd like to match. The function signature (return value) has two values, a string list containing positive matches and an error (in case we get one).

Inside the function body, we'll define a variable called matches which we'll use as the first return value. This variable will contain the list of matches.

func Glob(root, pattern string) ([]string, error) {
  var matches []string
}

Then, beneath the variable declaration, we'll call the filepath.Walk function and pass two arguments, the root path from the parent function and a callback function.

The callback function will specify two arguments, the file path, the file's info and an error (in case we got one).

err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {

})

Inside of the callback function, we'll do two things. First, because we only want files, we'll check if the current path is a directory and ignore it (via a return).

if info.IsDir() {
  return nil
}

Second, we'll match the pattern against the last element of the file path using filepath.Base. We'll check for an error and append each match to the matches list.

if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
  return err
} else if matched {
  matches = append(matches, path)
}

As the last statement of the parent function (Glob), we'll return two values: the matches and nil for the error.

return matches, nil

Finally, put it all together, factor in error handling and add comments, and we get the following function:

func Glob(root, pattern string) ([]string, error) {
  // create a list for the list of matches
  var matches []string
  // iterate through the directory
  err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    // return if an error occurred
    if err != nil {
      return err
    }
    // return if the current item is a directory
    if info.IsDir() {
      return nil
    }
    // check for a match against the last element of the file path
    if matched, err := filepath.Match(pattern, filepath.Base(path)); err != nil {
      // return an error if no match was found
      return err
    // append each match to the matches list
    } else if matched {
      matches = append(matches, path)
    }
    // return if no errors occurred
    return nil
  })
  // return if an error occurred
  if err != nil {
    return nil, err
  }
  // return the matches and no error
  return matches, nil
}

Usage

To use the Glob function, name two values, one for the file list and another for errors (if any occurred).

file_list, err := Glob("/root/", "*.txt")
if (error != nil) {
  // handle errors
}