Skip to content

Commit 4c09ea9

Browse files
committed
interp: Return invalid globs when nullglob is set
Prior to this commit invalid globs were returned as null when nullglob was set, which resulted in test expressions failing: $ shopt -s nullglob; [ -n butter ] && echo bubbles "-n": executable file not found in $PATH After this commit invalid globs are returned verbatim when nullglob is set.
1 parent be03afe commit 4c09ea9

File tree

3 files changed

+33
-16
lines changed

3 files changed

+33
-16
lines changed

expand/expand.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -409,14 +409,17 @@ func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) {
409409
for _, field := range wfields {
410410
path, doGlob := cfg.escapedGlobField(field)
411411
var matches []string
412+
var syntaxError *pattern.SyntaxError
412413
if doGlob && cfg.ReadDir != nil {
413414
matches, err = cfg.glob(dir, path)
414-
if err != nil {
415-
return nil, err
416-
}
417-
if len(matches) > 0 || cfg.NullGlob {
418-
fields = append(fields, matches...)
419-
continue
415+
if !errors.As(err, &syntaxError) {
416+
if err != nil {
417+
return nil, err
418+
}
419+
if len(matches) > 0 || cfg.NullGlob {
420+
fields = append(fields, matches...)
421+
continue
422+
}
420423
}
421424
}
422425
fields = append(fields, cfg.fieldJoin(field))
@@ -851,8 +854,7 @@ func (cfg *Config) glob(base, pat string) ([]string, error) {
851854
}
852855
expr, err := pattern.Regexp(part, pattern.Filenames)
853856
if err != nil {
854-
// If any glob part is not a valid pattern, don't glob.
855-
return nil, nil
857+
return nil, err
856858
}
857859
rx := regexp.MustCompile("^" + expr + "$")
858860
var newMatches []string

interp/interp_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -2581,6 +2581,12 @@ set +o pipefail
25812581
"shopt -s nullglob; touch existing-1; echo missing-* existing-*",
25822582
"existing-1\n",
25832583
},
2584+
// Ensure that setting nullglob does not return invalid globs as null
2585+
// strings.
2586+
{
2587+
"shopt -s nullglob; [ -n butter ] && echo bubbles",
2588+
"bubbles\n",
2589+
},
25842590
{
25852591
"cat <<EOF\n{foo_interp_missing,bar_interp_missing}\nEOF",
25862592
"{foo_interp_missing,bar_interp_missing}\n",

pattern/pattern.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ import (
2020
// Not all functions change their behavior with all of the options below.
2121
type Mode uint
2222

23+
type SyntaxError struct {
24+
msg string
25+
err error
26+
}
27+
28+
func (e SyntaxError) Error() string { return e.msg }
29+
30+
func (e SyntaxError) Unwrap() error { return e.err }
31+
2332
const (
2433
Shortest Mode = 1 << iota // prefer the shortest match.
2534
Filenames // "*" and "?" don't match slashes; only "**" does
@@ -85,13 +94,13 @@ writeLoop:
8594
}
8695
case '\\':
8796
if i++; i >= len(pat) {
88-
return "", fmt.Errorf(`\ at end of pattern`)
97+
return "", &SyntaxError{msg: `\ at end of pattern`}
8998
}
9099
buf.WriteString(regexp.QuoteMeta(string(pat[i])))
91100
case '[':
92101
name, err := charClass(pat[i:])
93102
if err != nil {
94-
return "", err
103+
return "", &SyntaxError{msg: "charClass invalid", err: err}
95104
}
96105
if name != "" {
97106
buf.WriteString(name)
@@ -110,19 +119,19 @@ writeLoop:
110119
}
111120
buf.WriteByte(c)
112121
if i++; i >= len(pat) {
113-
return "", fmt.Errorf("[ was not matched with a closing ]")
122+
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
114123
}
115124
switch c = pat[i]; c {
116125
case '!', '^':
117126
buf.WriteByte('^')
118127
if i++; i >= len(pat) {
119-
return "", fmt.Errorf("[ was not matched with a closing ]")
128+
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
120129
}
121130
}
122131
if c = pat[i]; c == ']' {
123132
buf.WriteByte(']')
124133
if i++; i >= len(pat) {
125-
return "", fmt.Errorf("[ was not matched with a closing ]")
134+
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
126135
}
127136
}
128137
rangeStart := byte(0)
@@ -140,7 +149,7 @@ writeLoop:
140149
break loopBracket
141150
}
142151
if rangeStart != 0 && rangeStart > c {
143-
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
152+
return "", &SyntaxError{msg: fmt.Sprintf("invalid range: %c-%c", rangeStart, c)}
144153
}
145154
if c == '-' {
146155
rangeStart = pat[i-1]
@@ -149,7 +158,7 @@ writeLoop:
149158
}
150159
}
151160
if i >= len(pat) {
152-
return "", fmt.Errorf("[ was not matched with a closing ]")
161+
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
153162
}
154163
case '{':
155164
if mode&Braces == 0 {
@@ -183,7 +192,7 @@ writeLoop:
183192
start, err1 := strconv.Atoi(match[1])
184193
end, err2 := strconv.Atoi(match[2])
185194
if err1 != nil || err2 != nil || start > end {
186-
return "", fmt.Errorf("invalid range: %q", match[0])
195+
return "", &SyntaxError{msg: fmt.Sprintf("invalid range: %q", match[0])}
187196
}
188197
// TODO: can we do better here?
189198
buf.WriteString("(?:")

0 commit comments

Comments
 (0)