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

@@ -58,6 +58,12 @@ func splitPipe(input string) []string {
case c == '"' && !inSingle && parenDepth == 0:
inDouble = !inDouble
current.WriteByte(c)
// Process substitution <(...): don't split | inside.
case c == '<' && !inSingle && !inDouble && i+1 < len(input) && input[i+1] == '(':
parenDepth++
current.WriteByte('<')
current.WriteByte('(')
i++
case c == '$' && !inSingle && !inDouble && i+1 < len(input) && input[i+1] == '(':
pendingDollar = true
current.WriteByte(c)
@@ -154,6 +160,15 @@ func (s *Shell) executeCommand(input string) error {
return nil
}
// { cmd; cmd; } command group
if strings.HasPrefix(input, "{") {
end := strings.LastIndex(input, "}")
if end > 0 {
inner := strings.TrimSpace(input[1:end])
return s.Execute(inner)
}
}
// Detect array assignment NAME=(...) or NAME+=(...)
if name, appendMode, elems, ok := s.parseArrayAssign(input); ok {
if appendMode {
@@ -264,6 +279,20 @@ func (s *Shell) extractRedirects(tokens []string) ([]string, []redirect) {
case strings.HasPrefix(tok, ">") && len(tok) > 1:
redirects = append(redirects, redirect{1, ">", tok[1:]})
i++
// <<< here-string
case tok == "<<<":
if i+1 < len(tokens) {
val := tokens[i+1] + "\n"
tmpf, err := os.CreateTemp("", "herestr*")
if err == nil {
tmpf.WriteString(val)
tmpf.Close()
redirects = append(redirects, redirect{0, "<", tmpf.Name()})
}
i += 2
} else {
i++
}
// < (stdin) — also handle process substitution <(...)
case tok == "<":
if i+1 < len(tokens) {
@@ -311,7 +340,11 @@ func (s *Shell) withRedirects(redirects []redirect, fn func() error) error {
}
}()
for _, r := range redirects {
for i, r := range redirects {
if r.dest == "/dev/null" {
redirects[i].dest = os.DevNull
r = redirects[i]
}
switch r.mode {
case ">":
if r.dest == "&1" {
@@ -361,6 +394,20 @@ func (s *Shell) executeExternal(cmdName string, args []string) error {
fmt.Fprintf(s.Stderr, "%s: command not found\n", cmdName)
return exitCodeErr{127}
}
// If the file is a bash/sh script, run it through our own interpreter.
// This avoids the CRLF shebang problem (#!/usr/bin/env bash\r) and lets
// us execute scripts that have no .exe extension on Windows.
if data, err := os.ReadFile(path); err == nil && isShellScript(data) {
sh := New()
sh.Stdin = s.Stdin
sh.Stdout = s.Stdout
sh.Stderr = s.Stderr
sh.SetArgs(args)
sh.SetVar("0", path)
return sh.Execute(string(data))
}
cmd := exec.Command(path, args...)
cmd.Stdin = s.Stdin
cmd.Stdout = s.Stdout
@@ -374,6 +421,25 @@ func (s *Shell) executeExternal(cmdName string, args []string) error {
return err
}
// isShellScript returns true when data begins with a #!/…bash or #!/…sh shebang.
func isShellScript(data []byte) bool {
if len(data) < 2 || data[0] != '#' || data[1] != '!' {
return false
}
nl := bytes.IndexByte(data, '\n')
if nl < 0 {
nl = len(data)
}
line := strings.TrimRight(string(data[:nl]), "\r")
for _, word := range strings.Fields(line[2:]) {
base := filepath.Base(word)
if base == "bash" || base == "sh" {
return true
}
}
return false
}
func (s *Shell) executeCommandBg(input string) error {
tokens := s.tokenize(input)
if len(tokens) == 0 {