fixed bash-for-windows
This commit is contained in:
@@ -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 != "" {
|
||||
|
||||
Reference in New Issue
Block a user