Implement piping between commands in backtick syntax (issue #06)

Add split_pipeline() to split command strings on unquoted | and
exec_pipeline() to fork N children connected by inter-stage pipes.
Only the last stage's stdout/stderr are captured; middle stages'
stderr is inherited. Exit code comes from the last stage.
This commit is contained in:
Cormac Shannon
2026-03-01 22:17:30 +00:00
parent 41b2095ed9
commit d0181e685d
3 changed files with 429 additions and 12 deletions

83
testes/lush/piping.lua Normal file
View File

@@ -0,0 +1,83 @@
-- testes/lush/piping.lua
-- Tests for piping between commands (issue #06).
print "testing piping"
-- basic pipe
do
local r = `echo hello | cat`
assert(r.stdout == "hello\n", "basic pipe stdout: " .. r.stdout)
assert(r.code == 0)
end
-- multi-stage pipe
do
local r = `printf "a\nb\nc\n" | grep b | cat`
assert(r.stdout == "b\n", "multi-stage pipe stdout: " .. r.stdout)
end
-- exit code is from last stage
do
local r = `echo hello | sh -c "exit 42"`
assert(r.code == 42, "exit code from last stage: " .. r.code)
end
-- pipe with first stage failing: cat gets EOF, succeeds
do
local r = `nonexistent_cmd_999 | cat`
assert(r.code == 0, "cat should succeed with EOF: " .. r.code)
end
-- quoted pipe character is NOT a pipe separator (double quotes)
do
local r = `echo "a|b"`
assert(r.stdout == "a|b\n", "double-quoted pipe: " .. r.stdout)
end
-- single-quoted pipe character is NOT a pipe separator
do
local r = `echo 'a|b'`
assert(r.stdout == "a|b\n", "single-quoted pipe: " .. r.stdout)
end
-- pipe with interpolation
do
local ext = ".lua"
local r = `echo "test.lua test.py" | grep "${ext}"`
assert(r.stdout == "test.lua test.py\n", "pipe with interpolation: " .. r.stdout)
end
-- stderr from middle stages goes to terminal, not captured
do
local r = `sh -c "echo err >&2; echo out" | cat`
assert(r.stdout == "out\n", "stdout through pipe: " .. r.stdout)
assert(r.stderr == "", "middle stderr not captured: '" .. r.stderr .. "'")
end
-- single command unchanged (regression check)
do
local r = `echo hello`
assert(r.stdout == "hello\n")
assert(r.code == 0)
end
-- pipe preserves multi-line output
do
local r = `printf "line1\nline2\nline3\n" | cat`
assert(r.stdout == "line1\nline2\nline3\n", "multiline pipe: " .. r.stdout)
end
-- pipe with whitespace around |
do
local r = `echo hello | cat`
assert(r.stdout == "hello\n", "whitespace around pipe: " .. r.stdout)
end
-- last stage stderr is captured
do
local r = `echo hello | sh -c "cat; echo piperr >&2"`
assert(r.stdout == "hello\n", "last stage stdout: " .. r.stdout)
assert(r.stderr == "piperr\n", "last stage stderr: " .. r.stderr)
end
print "OK"