Skip to content

Commit

Permalink
interp: Return invalid globs when nullglob is set
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lollipopman authored and mvdan committed May 23, 2022
1 parent cbb1e13 commit 77e3842
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 16 deletions.
18 changes: 10 additions & 8 deletions expand/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,14 +409,17 @@ func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) {
for _, field := range wfields {
path, doGlob := cfg.escapedGlobField(field)
var matches []string
var syntaxError *pattern.SyntaxError
if doGlob && cfg.ReadDir != nil {
matches, err = cfg.glob(dir, path)
if err != nil {
return nil, err
}
if len(matches) > 0 || cfg.NullGlob {
fields = append(fields, matches...)
continue
if !errors.As(err, &syntaxError) {
if err != nil {
return nil, err
}
if len(matches) > 0 || cfg.NullGlob {
fields = append(fields, matches...)
continue
}
}
}
fields = append(fields, cfg.fieldJoin(field))
Expand Down Expand Up @@ -851,8 +854,7 @@ func (cfg *Config) glob(base, pat string) ([]string, error) {
}
expr, err := pattern.Regexp(part, pattern.Filenames)
if err != nil {
// If any glob part is not a valid pattern, don't glob.
return nil, nil
return nil, err
}
rx := regexp.MustCompile("^" + expr + "$")
var newMatches []string
Expand Down
6 changes: 6 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2581,6 +2581,12 @@ set +o pipefail
"shopt -s nullglob; touch existing-1; echo missing-* existing-*",
"existing-1\n",
},
// Ensure that setting nullglob does not return invalid globs as null
// strings.
{
"shopt -s nullglob; [ -n butter ] && echo bubbles",
"bubbles\n",
},
{
"cat <<EOF\n{foo_interp_missing,bar_interp_missing}\nEOF",
"{foo_interp_missing,bar_interp_missing}\n",
Expand Down
25 changes: 17 additions & 8 deletions pattern/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import (
// Not all functions change their behavior with all of the options below.
type Mode uint

type SyntaxError struct {
msg string
err error
}

func (e SyntaxError) Error() string { return e.msg }

func (e SyntaxError) Unwrap() error { return e.err }

const (
Shortest Mode = 1 << iota // prefer the shortest match.
Filenames // "*" and "?" don't match slashes; only "**" does
Expand Down Expand Up @@ -85,13 +94,13 @@ writeLoop:
}
case '\\':
if i++; i >= len(pat) {
return "", fmt.Errorf(`\ at end of pattern`)
return "", &SyntaxError{msg: `\ at end of pattern`}
}
buf.WriteString(regexp.QuoteMeta(string(pat[i])))
case '[':
name, err := charClass(pat[i:])
if err != nil {
return "", err
return "", &SyntaxError{msg: "charClass invalid", err: err}
}
if name != "" {
buf.WriteString(name)
Expand All @@ -110,19 +119,19 @@ writeLoop:
}
buf.WriteByte(c)
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
}
switch c = pat[i]; c {
case '!', '^':
buf.WriteByte('^')
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
}
}
if c = pat[i]; c == ']' {
buf.WriteByte(']')
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
}
}
rangeStart := byte(0)
Expand All @@ -140,7 +149,7 @@ writeLoop:
break loopBracket
}
if rangeStart != 0 && rangeStart > c {
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
return "", &SyntaxError{msg: fmt.Sprintf("invalid range: %c-%c", rangeStart, c)}
}
if c == '-' {
rangeStart = pat[i-1]
Expand All @@ -149,7 +158,7 @@ writeLoop:
}
}
if i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
return "", &SyntaxError{msg: "[ was not matched with a closing ]"}
}
case '{':
if mode&Braces == 0 {
Expand Down Expand Up @@ -183,7 +192,7 @@ writeLoop:
start, err1 := strconv.Atoi(match[1])
end, err2 := strconv.Atoi(match[2])
if err1 != nil || err2 != nil || start > end {
return "", fmt.Errorf("invalid range: %q", match[0])
return "", &SyntaxError{msg: fmt.Sprintf("invalid range: %q", match[0])}
}
// TODO: can we do better here?
buf.WriteString("(?:")
Expand Down

0 comments on commit 77e3842

Please sign in to comment.