add brace expansion

- a{b,c}d, nested braces, empty alternatives (file{,.bak})
- sequences {1..5}, {5..1}, {1..10..3}, zero-padded {01..10}, letters
  {a..e}
- happens first on the command text, the results go through ~,
  variable and wildcard expansion
- quoted/escaped braces, ${...} and braces without comma or valid
  sequence are kept; \, protects a comma
- a redirection target must expand to one word (ambiguous redirect),
  a word may expand to at most 100000 words
- completion escapes { in file names

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Florian WaltherandClaude Opus 5.5 committed 2026-10-03 23:42:25 +02:00
1 parent da281ca4ab
commit b3311cbf94
4 files changed
+424 -1

No files matched your search

+8 -1
View File
@@ -815,7 +815,7 @@ func currentWord(s string) (word string, start int, quote byte) {
func escapeWord(s string) string {
var b strings.Builder
for _, r := range s {
if strings.ContainsRune(" \t\\'\";|&<>*?[$", r) {
if strings.ContainsRune(" \t\\'\";|&<>*?[${", r) {
b.WriteByte('\\')
}
b.WriteRune(r)
@@ -1354,6 +1354,12 @@ func execInput(input string) error {
// execCommand executes a pipeline of simple commands with redirections, in
// the background if background is set.
func execCommand(input string, background bool) error {
// Brace expansion comes first, so that its results are expanded further
input, err := expandBraceWords(input)
if err != nil {
return err
}
// Split the input into words and operators, expanding variables and ~ on
// the way.
tokens, err := tokenize(input)
@@ -1783,6 +1789,7 @@ Features:
override with $GOSH_HISTFILE); lines starting with a space are not saved
- Tab completion for commands and file paths
- Tilde expansion (~, ~/path) at the start of unquoted words
- Brace expansion: a{b,c}d -> abd acd, {1..5}, {01..10..2}, {a..e}
- Wildcards: * ? [abc] [a-z] [!abc] match file names (not hidden files
unless the pattern starts with a dot); quoted wildcards and values of
variables are not expanded, a pattern without match is kept as is