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