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
}