add subshells, here-documents and redirections/pipes for control structures
- control structures can have redirections (while read l; do ...; done
< file), which apply to all commands in them; they run in the shell
- in pipes and in the background they run in a subshell (cmd | while
read l; do ...; done, for ...; done &, a && b &)
- ( list ) subshells; ( and ) are operators now
- here-documents <<EOF, <<-EOF, <<'EOF' and here-strings <<<word
- redirections of source apply to the commands of the file
- fix non-ASCII characters in quotes being mangled ('für')
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
443367f66f
commit
f66cf538c0
7 files changed
+1172
-221
No files matched your search
+49
-5
@@ -1437,6 +1437,26 @@ func pipelineString(pl *pipelineNode) string {
|
||||
s += "while "
|
||||
}
|
||||
s += treeString(n.cond) + " do " + treeString(n.body) + " done"
|
||||
case *caseNode:
|
||||
s += "case " + n.word + " in"
|
||||
for _, c := range n.clauses {
|
||||
s += " (" + strings.Join(c.patterns, "|") + ") " + treeString(c.body) + " ;;"
|
||||
}
|
||||
s += " esac"
|
||||
case *groupNode:
|
||||
s += "{ " + treeString(n.body) + " }"
|
||||
case *subshellNode:
|
||||
s += "(" + n.text + ")"
|
||||
case *redirNode:
|
||||
s += pipelineString(&pipelineNode{cmd: n.cmd}) + " <" + n.redirs + ">"
|
||||
case *pipeNode:
|
||||
var elems []string
|
||||
for _, e := range n.elems {
|
||||
elems = append(elems, "<"+e.text+">")
|
||||
}
|
||||
s += strings.Join(elems, " | ")
|
||||
case *funcDefNode:
|
||||
s += n.def.name + "() " + pipelineString(&pipelineNode{cmd: n.def.body})
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -1504,6 +1524,27 @@ func TestParse_ControlStructures(t *testing.T) {
|
||||
{"while a; do if b; then break; fi; done", "while [a] do if [b] then [break] fi done"},
|
||||
{"for i in 1 2; do for j in 3 4; do echo $i$j; done; done", "for i in (1 2) do for j in (3 4) do [echo $i$j] done done"},
|
||||
{"if a; then if b; then c; fi fi", "if [a] then if [b] then [c] fi fi"},
|
||||
// Compound commands in pipelines, with redirections, in the background
|
||||
{"for x in a; do b; done | cat", "<for x in a; do b; done> | <cat>"},
|
||||
{"a | while b; do c; done | d", "<a> | <while b; do c; done> | <d>"},
|
||||
{"if a; then b; fi > out 2>&1", "if [a] then [b] fi <> out 2>&1>"},
|
||||
{"while read l; do b; done < in", "while [read l] do [b] done << in>"},
|
||||
{"{ a; b; } 2> err", "{ [a]; [b] } <2> err>"},
|
||||
{"for x in a; do b; done &", "for x in (a) do [b] done &"},
|
||||
{"a && b &", "[a] && [b] &"},
|
||||
// Subshells
|
||||
{"(a; b)", "(a; b)"},
|
||||
{"( a ) | b", "<( a )> | <b>"},
|
||||
{"(a) > f", "(a) <> f>"},
|
||||
{"(a; (b)) && c", "(a; (b)) && [c]"},
|
||||
// case, groups and functions
|
||||
{"case $x in a|b) c;; (d) e; f;; esac", "case $x in (a|b) [c] ;; (d) [e]; [f] ;; esac"},
|
||||
{"case x in\n a)\n b\n ;;\nesac", "case x in (a) [b] ;; esac"},
|
||||
{"{ a; }", "{ [a] }"},
|
||||
{"f() { a; }", "f() { [a] }"},
|
||||
{"f ( ) { a; }", "f() { [a] }"},
|
||||
{"function f { a; }", "f() { [a] }"},
|
||||
{"f() (a)", "f() (a)"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tree, err := parse(tc.input)
|
||||
@@ -1522,6 +1563,8 @@ func TestParse_Incomplete(t *testing.T) {
|
||||
"if a", "if a; then", "if a; then b", "if a; then b; else", "if a; then b; elif c",
|
||||
"for x", "for x in a b", "for x in a; do", "for x in a; do b",
|
||||
"while a", "while a; do b", "until a; do",
|
||||
"(a", "(a; b", "{ a;", "case x in", "case x in a) b;;", "f() {", "f()",
|
||||
"cat <<EOF", "cat <<EOF\nbody", "cat <<'EOF'\nbody\nEO",
|
||||
"a &&", "a ||", "a |", "echo 'open", `echo "open`, "echo $(a", "echo a \\",
|
||||
} {
|
||||
if _, err := parse(input); !errors.Is(err, errIncomplete) {
|
||||
@@ -1536,6 +1579,9 @@ func TestParse_SyntaxErrors(t *testing.T) {
|
||||
"fi", "then", "done", "if a; then fi", "if ; then a; fi", "while a; do done",
|
||||
"if a; then b; done", "for 1x in a; do b; done", "for x in a do b; done; x",
|
||||
"a | | b", "| a",
|
||||
"()", "a (", "a )", "echo (a)", ")", "(a) b", "if a; then b; fi c", "{ a; } b",
|
||||
"case x in a b) c;; esac", "case x in ) a;; esac", "cat <<", "cat << ;",
|
||||
"f() { a; } | b",
|
||||
} {
|
||||
_, err := parse(input)
|
||||
if err == nil || errors.Is(err, errIncomplete) {
|
||||
@@ -1543,11 +1589,9 @@ func TestParse_SyntaxErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
for input, want := range map[string]string{
|
||||
"for x in a; do b; done | cat": "control structures in pipelines are not supported",
|
||||
"echo | while a; do b; done": "control structures in pipelines are not supported",
|
||||
"if a; then b; fi > out": "redirections of control structures are not supported (near `>')",
|
||||
"for x in a; do b; done &": "only simple commands and pipelines can run in the background",
|
||||
"a && b &": "only simple commands and pipelines can run in the background",
|
||||
"f() { a; } | b": "function definitions in pipelines are not supported",
|
||||
"f() { a; } > out": "redirections of function definitions are not supported",
|
||||
"if a; then b; fi c": "syntax error near unexpected token `c'",
|
||||
} {
|
||||
if _, err := parse(input); err == nil || err.Error() != want {
|
||||
t.Errorf("parse(%q) = %v, want %q", input, err, want)
|
||||
|
||||
Reference in new issue
Block a user