fixed bash-for-windows

This commit is contained in:
Cametendo
2026-05-31 21:49:13 +02:00
parent 7b3a101946
commit 9037606447
8 changed files with 437 additions and 123 deletions

View File

@@ -82,7 +82,6 @@ func (s *Shell) expandWord(word string) string {
result.WriteByte('$')
break
}
// $'...' ANSI C string
if word[i] == '\'' {
i++ // skip opening '
@@ -232,7 +231,11 @@ func (s *Shell) expandWord(word string) string {
result.WriteString(s.vars["#"])
i++
case '@':
result.WriteString(strings.Join(s.args, "\x01"))
if len(s.args) == 0 {
result.WriteString("\x01") // empty-expansion sentinel → 0 tokens
} else {
result.WriteString(strings.Join(s.args, "\x01"))
}
i++
case '*':
result.WriteString(s.vars["*"])
@@ -278,29 +281,95 @@ func (s *Shell) evalVarExpr(expr string) string {
return strconv.Itoa(len(s.getVar(rest)))
}
// Array indexing: ${arr[@]}, ${arr[*]}, ${arr[N]}
if bracketIdx := strings.Index(expr, "["); bracketIdx >= 0 && strings.HasSuffix(expr, "]") {
// Make sure there's no operator before the bracket
prefix := expr[:bracketIdx]
hasOp := strings.ContainsAny(prefix, ":-:=:+%#")
if !hasOp {
arrName := prefix
idx := expr[bracketIdx+1 : len(expr)-1]
if idx == "@" {
arr := s.getArray(arrName)
return strings.Join(arr, "\x01")
// Array indexing: ${arr[@]}, ${arr[*]}, ${arr[N]}, ${arr[N]:-default}, etc.
// Find the bracket pair and evaluate the array access even when an operator
// (:- := :+) follows the closing ].
if bracketIdx := strings.Index(expr, "["); bracketIdx >= 0 {
closeIdx := -1
// Find matching ] — must balance nested $((…)) parens inside the index
depth := 0
for k := bracketIdx + 1; k < len(expr); k++ {
if expr[k] == '(' {
depth++
} else if expr[k] == ')' {
depth--
} else if expr[k] == ']' && depth == 0 {
closeIdx = k
break
}
if idx == "*" {
arr := s.getArray(arrName)
return strings.Join(arr, " ")
}
if closeIdx > bracketIdx {
prefix := expr[:bracketIdx]
// No operator before the bracket
if !strings.ContainsAny(prefix, ":=+%#") {
arrName := prefix
idx := expr[bracketIdx+1 : closeIdx]
rest := expr[closeIdx+1:] // may be empty or ":-…" / ":+…" / ":=…"
arrVal := ""
isMulti := false
multiVal := ""
switch idx {
case "@":
arr := s.getArray(arrName)
if len(arr) == 0 {
arrVal = ""
} else {
isMulti = true
multiVal = strings.Join(arr, "\x01")
}
case "*":
arr := s.getArray(arrName)
arrVal = strings.Join(arr, " ")
default:
n := s.evalArith(idx)
arr := s.getArray(arrName)
if n >= 0 && n < len(arr) {
arrVal = arr[n]
}
}
// Apply trailing operator (:- := :+) if present
switch {
case rest == "":
if isMulti {
if multiVal == "" {
return "\x01"
}
return multiVal
}
return arrVal
case strings.HasPrefix(rest, ":-"):
if isMulti {
return multiVal
}
if arrVal != "" {
return arrVal
}
return s.expandWord(rest[2:])
case strings.HasPrefix(rest, ":="):
if arrVal != "" {
return arrVal
}
expanded := s.expandWord(rest[2:])
s.vars[arrName] = expanded
return expanded
case strings.HasPrefix(rest, ":+"):
if isMulti {
return s.expandWord(rest[2:])
}
if arrVal != "" {
return s.expandWord(rest[2:])
}
return ""
default:
if isMulti {
return multiVal
}
return arrVal
}
}
// Numeric index
n := s.evalArith(idx)
arr := s.getArray(arrName)
if n >= 0 && n < len(arr) {
return arr[n]
}
return ""
}
}
@@ -576,6 +645,17 @@ func (s *Shell) tokenize(input string) []string {
inDouble = !inDouble
wasQuoted = true
current.WriteByte(c)
// Process substitution <(...): when '<' is immediately followed by '('
// (no space between), keep the entire <(cmd args...) as one token so
// extractRedirects can run the command and redirect stdin to its output.
// The standalone '<' redirect operator always has a space after it and
// therefore becomes its own token before this case is reached.
case c == '<' && !inSingle && !inDouble && parenDepth == 0 &&
i+1 < len(input) && input[i+1] == '(':
current.WriteByte('<')
current.WriteByte('(')
parenDepth++
i++ // skip '('; outer loop will add 1 more → 2 chars consumed
case c == '$' && !inSingle && i+1 < len(input) && (input[i+1] == '(' || input[i+1] == '{'):
// Mark that the next ( opens a substitution — don't increment depth here
if input[i+1] == '(' {
@@ -633,8 +713,17 @@ doneTokenizing:
tok = tok[2:]
}
expanded := s.expandWord(tok)
// Handle multi-word expansion from $@ and ${arr[@]}
// Handle multi-word expansion from $@ and ${arr[@]}.
// Exception: process substitution tokens (<(...)) must stay as one
// token so extractRedirects can recognise them. Inside a process
// substitution the \x01 separators are argument boundaries for the
// inner command, so join them with spaces instead of splitting.
if strings.Contains(expanded, "\x01") {
if strings.HasPrefix(expanded, "<(") {
expanded = strings.ReplaceAll(expanded, "\x01", " ")
result = append(result, expanded)
continue
}
parts := strings.Split(expanded, "\x01")
for _, p := range parts {
if p != "" {