1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
16 // ErrBadPattern indicates a pattern was malformed.
17 var ErrBadPattern
= errors
.New("syntax error in pattern")
19 // Match reports whether name matches the shell file name pattern.
20 // The pattern syntax is:
25 // '*' matches any sequence of non-Separator characters
26 // '?' matches any single non-Separator character
27 // '[' [ '^' ] { character-range } ']'
28 // character class (must be non-empty)
29 // c matches character c (c != '*', '?', '\\', '[')
30 // '\\' c matches character c
33 // c matches character c (c != '\\', '-', ']')
34 // '\\' c matches character c
35 // lo '-' hi matches character c for lo <= c <= hi
37 // Match requires pattern to match all of name, not just a substring.
38 // The only possible returned error is ErrBadPattern, when pattern
41 // On Windows, escaping is disabled. Instead, '\\' is treated as
44 func Match(pattern
, name
string) (matched
bool, err error
) {
46 for len(pattern
) > 0 {
49 star
, chunk
, pattern
= scanChunk(pattern
)
50 if star
&& chunk
== "" {
51 // Trailing * matches rest of string unless it has a /.
52 return !strings
.Contains(name
, string(Separator
)), nil
54 // Look for match at current position.
55 t
, ok
, err
:= matchChunk(chunk
, name
)
56 // if we're the last chunk, make sure we've exhausted the name
57 // otherwise we'll give a false result even if we could still match
59 if ok
&& (len(t
) == 0 ||
len(pattern
) > 0) {
67 // Look for match skipping i+1 bytes.
69 for i
:= 0; i
< len(name
) && name
[i
] != Separator
; i
++ {
70 t
, ok
, err
:= matchChunk(chunk
, name
[i
+1:])
72 // if we're the last chunk, make sure we exhausted the name
73 if len(pattern
) == 0 && len(t
) > 0 {
86 return len(name
) == 0, nil
89 // scanChunk gets the next segment of pattern, which is a non-star string
90 // possibly preceded by a star.
91 func scanChunk(pattern
string) (star
bool, chunk
, rest
string) {
92 for len(pattern
) > 0 && pattern
[0] == '*' {
99 for i
= 0; i
< len(pattern
); i
++ {
102 if runtime
.GOOS
!= "windows" {
103 // error check handled in matchChunk: bad pattern.
104 if i
+1 < len(pattern
) {
118 return star
, pattern
[0:i
], pattern
[i
:]
121 // matchChunk checks whether chunk matches the beginning of s.
122 // If so, it returns the remainder of s (after the match).
123 // Chunk is all single-character operators: literals, char classes, and ?.
124 func matchChunk(chunk
, s
string) (rest
string, ok
bool, err error
) {
125 // failed records whether the match has failed.
126 // After the match fails, the loop continues on processing chunk,
127 // checking that the pattern is well-formed but no longer reading s.
130 if !failed
&& len(s
) == 0 {
139 r
, n
= utf8
.DecodeRuneInString(s
)
145 if len(chunk
) > 0 && chunk
[0] == '^' {
153 if len(chunk
) > 0 && chunk
[0] == ']' && nrange
> 0 {
158 if lo
, chunk
, err
= getEsc(chunk
); err
!= nil {
159 return "", false, err
163 if hi
, chunk
, err
= getEsc(chunk
[1:]); err
!= nil {
164 return "", false, err
167 if lo
<= r
&& r
<= hi
{
172 if match
== negated
{
178 if s
[0] == Separator
{
181 _
, n
:= utf8
.DecodeRuneInString(s
)
187 if runtime
.GOOS
!= "windows" {
190 return "", false, ErrBadPattern
197 if chunk
[0] != s
[0] {
206 return "", false, nil
211 // getEsc gets a possibly-escaped character from chunk, for a character class.
212 func getEsc(chunk
string) (r rune
, nchunk
string, err error
) {
213 if len(chunk
) == 0 || chunk
[0] == '-' || chunk
[0] == ']' {
217 if chunk
[0] == '\\' && runtime
.GOOS
!= "windows" {
224 r
, n
:= utf8
.DecodeRuneInString(chunk
)
225 if r
== utf8
.RuneError
&& n
== 1 {
229 if len(nchunk
) == 0 {
235 // Glob returns the names of all files matching pattern or nil
236 // if there is no matching file. The syntax of patterns is the same
237 // as in Match. The pattern may describe hierarchical names such as
238 // /usr/*/bin/ed (assuming the Separator is '/').
240 // Glob ignores file system errors such as I/O errors reading directories.
241 // The only possible returned error is ErrBadPattern, when pattern
243 func Glob(pattern
string) (matches
[]string, err error
) {
244 // Check pattern is well-formed.
245 if _
, err
:= Match(pattern
, ""); err
!= nil {
248 if !hasMeta(pattern
) {
249 if _
, err
= os
.Lstat(pattern
); err
!= nil {
252 return []string{pattern
}, nil
255 dir
, file
:= Split(pattern
)
257 if runtime
.GOOS
== "windows" {
258 volumeLen
, dir
= cleanGlobPathWindows(dir
)
260 dir
= cleanGlobPath(dir
)
263 if !hasMeta(dir
[volumeLen
:]) {
264 return glob(dir
, file
, nil)
267 // Prevent infinite recursion. See issue 15879.
269 return nil, ErrBadPattern
277 for _
, d
:= range m
{
278 matches
, err
= glob(d
, file
, matches
)
286 // cleanGlobPath prepares path for glob matching.
287 func cleanGlobPath(path
string) string {
291 case string(Separator
):
292 // do nothing to the path
295 return path
[0 : len(path
)-1] // chop off trailing separator
299 // cleanGlobPathWindows is windows version of cleanGlobPath.
300 func cleanGlobPathWindows(path
string) (prefixLen
int, cleaned
string) {
301 vollen
:= volumeNameLen(path
)
305 case vollen
+1 == len(path
) && os
.IsPathSeparator(path
[len(path
)-1]): // /, \, C:\ and C:/
306 // do nothing to the path
307 return vollen
+ 1, path
308 case vollen
== len(path
) && len(path
) == 2: // C:
309 return vollen
, path
+ "." // convert C: into C:.
311 if vollen
>= len(path
) {
312 vollen
= len(path
) - 1
314 return vollen
, path
[0 : len(path
)-1] // chop off trailing separator
318 // glob searches for files matching pattern in the directory dir
319 // and appends them to matches. If the directory cannot be
320 // opened, it returns the existing matches. New matches are
321 // added in lexicographical order.
322 func glob(dir
, pattern
string, matches
[]string) (m
[]string, e error
) {
324 fi
, err
:= os
.Stat(dir
)
326 return // ignore I/O error
329 return // ignore I/O error
331 d
, err
:= os
.Open(dir
)
333 return // ignore I/O error
337 names
, _
:= d
.Readdirnames(-1)
340 for _
, n
:= range names
{
341 matched
, err
:= Match(pattern
, n
)
346 m
= append(m
, Join(dir
, n
))
352 // hasMeta reports whether path contains any of the magic characters
353 // recognized by Match.
354 func hasMeta(path
string) bool {
356 if runtime
.GOOS
!= "windows" {
359 return strings
.ContainsAny(path
, magicChars
)