fixed bash-for-windows
This commit is contained in:
@@ -119,6 +119,8 @@ func (s *Shell) SetVar(name, value string) {
|
||||
}
|
||||
|
||||
// Execute runs commands from the given input string.
|
||||
func PreprocessForTest(input string) string { return preprocessHeredocs(input) }
|
||||
func ParseBlocksForTest(input string) []string { return parseBlocks(input) }
|
||||
func (s *Shell) Execute(input string) error {
|
||||
// Normalize CRLF to LF
|
||||
input = strings.ReplaceAll(input, "\r\n", "\n")
|
||||
@@ -206,9 +208,9 @@ func parseBlocks(input string) []string {
|
||||
funcKwDepth = 0
|
||||
for _, p := range splitStatements(stmt[braceIdx+1:]) {
|
||||
switch firstWord(p) {
|
||||
case "if", "for", "while", "until", "case":
|
||||
case "if", "for", "while", "until", "case", "{":
|
||||
funcKwDepth++
|
||||
case "fi", "done", "esac":
|
||||
case "fi", "done", "esac", "}":
|
||||
funcKwDepth--
|
||||
}
|
||||
}
|
||||
@@ -226,7 +228,7 @@ func parseBlocks(input string) []string {
|
||||
}
|
||||
|
||||
switch w {
|
||||
case "if", "for", "while", "until", "case":
|
||||
case "if", "for", "while", "until", "case", "{":
|
||||
kwDepth++
|
||||
}
|
||||
kwDepth += embeddedKwDepth(stmt)
|
||||
@@ -255,9 +257,9 @@ func parseBlocks(input string) []string {
|
||||
continue
|
||||
}
|
||||
switch w {
|
||||
case "if", "for", "while", "until", "case":
|
||||
case "if", "for", "while", "until", "case", "{":
|
||||
funcKwDepth++
|
||||
case "fi", "done", "esac":
|
||||
case "fi", "done", "esac", "}":
|
||||
funcKwDepth--
|
||||
}
|
||||
funcKwDepth += embeddedKwDepth(stmt)
|
||||
@@ -273,6 +275,7 @@ func parseBlocks(input string) []string {
|
||||
// embeddedKwDepth returns the net depth change from keywords that appear
|
||||
// after do/then/else/elif within a single statement (excluding the first word,
|
||||
// which is handled separately by the caller).
|
||||
func EmbeddedKwDepthForTest(s string) int { return embeddedKwDepth(s) }
|
||||
func embeddedKwDepth(stmt string) int {
|
||||
words := strings.Fields(stmt)
|
||||
delta := 0
|
||||
@@ -375,6 +378,12 @@ func parseHeredocMarkers(line string) (string, []string) {
|
||||
out.WriteByte(c)
|
||||
i++
|
||||
case c == '<' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '<':
|
||||
// <<< here-string: not a heredoc, pass through all three < chars unchanged
|
||||
if i+2 < len(line) && line[i+2] == '<' {
|
||||
out.WriteString("<<<")
|
||||
i += 3
|
||||
break
|
||||
}
|
||||
// Possible heredoc
|
||||
i += 2 // skip <<
|
||||
stripTabs := false
|
||||
@@ -429,6 +438,7 @@ func splitStatements(input string) []string {
|
||||
inSingle := false
|
||||
inDouble := false
|
||||
parenDepth := 0
|
||||
braceDepth := 0 // { } command groups — don't split ; inside them
|
||||
|
||||
for i := 0; i < len(input); i++ {
|
||||
c := input[i]
|
||||
@@ -445,7 +455,14 @@ func splitStatements(input string) []string {
|
||||
case c == ')' && !inSingle && !inDouble && parenDepth > 0:
|
||||
parenDepth--
|
||||
current.WriteByte(c)
|
||||
case c == ';' && !inSingle && !inDouble && parenDepth == 0:
|
||||
// Track { } command groups but not ${...} variable expansions
|
||||
case c == '{' && !inSingle && !inDouble && parenDepth == 0 && (i == 0 || input[i-1] != '$'):
|
||||
braceDepth++
|
||||
current.WriteByte(c)
|
||||
case c == '}' && !inSingle && !inDouble && parenDepth == 0 && braceDepth > 0:
|
||||
braceDepth--
|
||||
current.WriteByte(c)
|
||||
case c == ';' && !inSingle && !inDouble && parenDepth == 0 && braceDepth == 0:
|
||||
if i+1 < len(input) && input[i+1] == ';' {
|
||||
// Double semicolon — flush current token, then emit ";;" as a token
|
||||
if s := strings.TrimSpace(current.String()); s != "" {
|
||||
@@ -461,7 +478,7 @@ func splitStatements(input string) []string {
|
||||
}
|
||||
current.Reset()
|
||||
}
|
||||
case c == '\n' && !inSingle && !inDouble && parenDepth == 0:
|
||||
case c == '\n' && !inSingle && !inDouble && parenDepth == 0 && braceDepth == 0:
|
||||
if s := strings.TrimSpace(current.String()); s != "" {
|
||||
result = append(result, s)
|
||||
}
|
||||
@@ -553,12 +570,15 @@ func (s *Shell) executeBlock(block string) error {
|
||||
if isFuncDefStart(block) {
|
||||
return s.defineFunction(block)
|
||||
}
|
||||
for _, line := range strings.Split(block, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
// Use splitStatements instead of strings.Split("\n") so that multi-line
|
||||
// constructs (e.g. process substitutions spanning several lines) are kept
|
||||
// together as a single logical unit rather than being broken apart.
|
||||
for _, stmt := range splitStatements(block) {
|
||||
stmt = strings.TrimSpace(stmt)
|
||||
if stmt == "" || strings.HasPrefix(stmt, "#") {
|
||||
continue
|
||||
}
|
||||
if err := s.executeLine(line); err != nil {
|
||||
if err := s.executeLine(stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -591,20 +611,40 @@ func splitBySemicolon(line string) []string {
|
||||
current := strings.Builder{}
|
||||
inSingle := false
|
||||
inDouble := false
|
||||
parenDepth := 0 // tracks $(...) and <(...) nesting
|
||||
pendingDollar := false
|
||||
|
||||
for i := 0; i < len(line); i++ {
|
||||
c := line[i]
|
||||
switch {
|
||||
case c == '\'' && !inDouble:
|
||||
case c == '\'' && !inDouble && parenDepth == 0:
|
||||
inSingle = !inSingle
|
||||
current.WriteByte(c)
|
||||
case c == '"' && !inSingle:
|
||||
case c == '"' && !inSingle && parenDepth == 0:
|
||||
inDouble = !inDouble
|
||||
current.WriteByte(c)
|
||||
case c == ';' && !inSingle && !inDouble:
|
||||
// Process substitution <(...): don't split ; | && || inside.
|
||||
case c == '<' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '(':
|
||||
parenDepth++
|
||||
current.WriteByte('<')
|
||||
current.WriteByte('(')
|
||||
i++
|
||||
// Command substitution $(...): don't split ; inside.
|
||||
case c == '$' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '(':
|
||||
pendingDollar = true
|
||||
current.WriteByte(c)
|
||||
case c == '(' && !inSingle && !inDouble && (parenDepth > 0 || pendingDollar):
|
||||
parenDepth++
|
||||
pendingDollar = false
|
||||
current.WriteByte(c)
|
||||
case c == ')' && !inSingle && !inDouble && parenDepth > 0:
|
||||
parenDepth--
|
||||
current.WriteByte(c)
|
||||
case c == ';' && !inSingle && !inDouble && parenDepth == 0:
|
||||
parts = append(parts, current.String())
|
||||
current.Reset()
|
||||
default:
|
||||
pendingDollar = false
|
||||
current.WriteByte(c)
|
||||
}
|
||||
}
|
||||
@@ -624,38 +664,59 @@ func (s *Shell) executeAndOrList(line string) error {
|
||||
op := ""
|
||||
inSingle := false
|
||||
inDouble := false
|
||||
dbDepth := 0 // double-bracket [[ depth
|
||||
dbDepth := 0 // double-bracket [[ depth
|
||||
parenDepth := 0 // $( ) depth — don't split && || inside subshells
|
||||
pendingDollar := false
|
||||
|
||||
for i := 0; i < len(line); i++ {
|
||||
c := line[i]
|
||||
switch {
|
||||
case c == '\'' && !inDouble:
|
||||
case c == '\'' && !inDouble && parenDepth == 0:
|
||||
inSingle = !inSingle
|
||||
current.WriteByte(c)
|
||||
case c == '"' && !inSingle:
|
||||
case c == '"' && !inSingle && parenDepth == 0:
|
||||
inDouble = !inDouble
|
||||
current.WriteByte(c)
|
||||
case c == '[' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '[':
|
||||
// Process substitution <(...): don't split && || inside.
|
||||
case c == '<' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '(':
|
||||
parenDepth++
|
||||
current.WriteByte('<')
|
||||
current.WriteByte('(')
|
||||
i++
|
||||
case c == '$' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '(':
|
||||
pendingDollar = true
|
||||
current.WriteByte(c)
|
||||
case c == '(' && !inSingle && !inDouble && (parenDepth > 0 || pendingDollar):
|
||||
parenDepth++
|
||||
pendingDollar = false
|
||||
current.WriteByte(c)
|
||||
case c == ')' && !inSingle && !inDouble && parenDepth > 0:
|
||||
parenDepth--
|
||||
current.WriteByte(c)
|
||||
case c == '[' && !inSingle && !inDouble && parenDepth == 0 && i+1 < len(line) && line[i+1] == '[':
|
||||
dbDepth++
|
||||
current.WriteByte(c)
|
||||
current.WriteByte(line[i+1])
|
||||
i++
|
||||
case c == ']' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == ']' && dbDepth > 0:
|
||||
case c == ']' && !inSingle && !inDouble && parenDepth == 0 && i+1 < len(line) && line[i+1] == ']' && dbDepth > 0:
|
||||
dbDepth--
|
||||
current.WriteByte(c)
|
||||
current.WriteByte(line[i+1])
|
||||
i++
|
||||
case c == '&' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '&' && dbDepth == 0:
|
||||
case c == '&' && !inSingle && !inDouble && parenDepth == 0 && i+1 < len(line) && line[i+1] == '&' && dbDepth == 0:
|
||||
pendingDollar = false
|
||||
tokens = append(tokens, tok{current.String(), op})
|
||||
current.Reset()
|
||||
op = "&&"
|
||||
i++
|
||||
case c == '|' && !inSingle && !inDouble && i+1 < len(line) && line[i+1] == '|' && dbDepth == 0:
|
||||
case c == '|' && !inSingle && !inDouble && parenDepth == 0 && i+1 < len(line) && line[i+1] == '|' && dbDepth == 0:
|
||||
pendingDollar = false
|
||||
tokens = append(tokens, tok{current.String(), op})
|
||||
current.Reset()
|
||||
op = "||"
|
||||
i++
|
||||
default:
|
||||
pendingDollar = false
|
||||
current.WriteByte(c)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user