diff --git a/expand/expand.go b/expand/expand.go index bd809953e..2619d846f 100644 --- a/expand/expand.go +++ b/expand/expand.go @@ -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)) @@ -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 diff --git a/interp/interp_test.go b/interp/interp_test.go index d65e58b77..3f248a26e 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -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 <= 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) @@ -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) @@ -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] @@ -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 { @@ -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("(?:")