procstream
stableSpawn a subprocess with piped stdio and stream its stdout and stderr line-by-line without blocking, while writing to its stdin.
use plugin procstream::{Proc.spawn, read_lines, read_err_lines, …} Functions (7)
- Proc.spawn Spawn a subprocess with piped stdio and return a `Proc` handle
- read_lines Drain and return the currently-queued stdout lines
- read_err_lines Drain and return the currently-queued stderr lines
- write Write data to the child's stdin and return the byte count
- alive Report whether the process is still running
- exit_code Return the process exit code, or -1 if not yet exited
- kill Terminate the child process
Overview
procstream fills the one gap in Zolo's blocking std::process: incremental,
non-blocking access to a child process's output while it is still running. You
spawn a command with Proc.spawn, which returns a Proc handle, then poll
stdout and stderr line-by-line as the process produces them — no waiting for the
whole program to finish.
The mental model is a stateful handle backed by background reader threads. Each
call to read_lines (or read_err_lines) drains whatever output has accumulated
so far and returns it as an array of strings, leaving the queue empty for next
time. Alongside reading you can push data to the child's stdin with write,
check whether it is still running with alive, retrieve its exit_code once it
has finished, and stop it early with kill. Reach for procstream whenever you
need to drive an interactive or long-running CLI tool and react to its output as
it streams.
Common patterns
Spawn a command and drain its stdout until it exits:
use plugin procstream::{Proc}
let p = Proc.spawn("ping", ["-n", "3", "127.0.0.1"])
while p.alive() {
for line in p.read_lines() {
print(line)
}
}
// flush any lines produced just before exit
for line in p.read_lines() {
print(line)
}
print("exit code: {p.exit_code()}")
Run a process in a working directory and separate stdout from stderr:
use plugin procstream::{Proc}
let p = Proc.spawn("git", ["status"], #{ "cwd": "/tmp/repo" })
while p.alive() {
for out in p.read_lines() { print("out: {out}") }
for err in p.read_err_lines() { print("err: {err}") }
}
Feed an interactive tool through its stdin and read the response:
use plugin procstream::{Proc}
let p = Proc.spawn("cat", [])
p.write("hello\n")
p.write("world\n")
for echoed in p.read_lines() {
print("echo: {echoed}")
}
p.kill()
Spawn a subprocess with piped stdio and return a `Proc` handle
Spawns cmd with the given argument array and returns a Proc handle. Stdout
and stderr are piped and read in the background, and stdin is piped so write
works. The optional opts table accepts a cwd string (working directory) and
a stdin string — set stdin to "null" to connect the child's stdin to the
null device so tools that probe stdin don't block (in that mode write becomes a
no-op returning 0).
use plugin procstream::{Proc}
let p = Proc.spawn("echo", ["hello from procstream"])
for line in p.read_lines() {
print(line)
}
Pass an opts table to set the working directory or detach stdin:
use plugin procstream::{Proc}
let p = Proc.spawn("ls", ["-la"], #{ "cwd": "/usr", "stdin": "null" })
while p.alive() {
for line in p.read_lines() { print(line) }
}
Drain and return the currently-queued stdout lines
Drains and returns every stdout line buffered since the last call, as an array of
strings. The queue is emptied, so a subsequent call returns only newly arrived
lines. Returns an empty array when no output is pending. Because reading happens
on a background thread, poll this in a loop while the process is alive and once
more after it exits to catch the final lines.
use plugin procstream::{Proc}
let p = Proc.spawn("printf", ["a\nb\nc\n"])
while p.alive() {
for line in p.read_lines() { print("got: {line}") }
}
for line in p.read_lines() { print("final: {line}") }
Drain and return the currently-queued stderr lines
Drains and returns every stderr line buffered since the last call, as an array of
strings. Identical in behavior to read_lines but reads the error stream,
letting you keep diagnostics separate from normal output.
use plugin procstream::{Proc}
let p = Proc.spawn("cmd_that_warns", [])
while p.alive() {
for out in p.read_lines() { print("out: {out}") }
for err in p.read_err_lines() { print("warn: {err}") }
}
Write data to the child's stdin and return the byte count
Writes data to the child's stdin exactly as given (no newline is appended) and
flushes immediately, returning the number of bytes written. Add a trailing \n
yourself when the tool reads line-by-line. If the process was spawned with
stdin: "null" or its stdin is otherwise closed, write is a safe no-op and
returns 0.
use plugin procstream::{Proc}
let p = Proc.spawn("cat", [])
let n = p.write("a line of input\n")
print("wrote {n} bytes")
for line in p.read_lines() { print(line) }
Send several lines, then read the combined response:
use plugin procstream::{Proc}
let p = Proc.spawn("sort", [])
p.write("banana\n")
p.write("apple\n")
p.write("cherry\n")
p.kill()
for line in p.read_lines() { print(line) }
Report whether the process is still running
Returns true while the child process is still running and false once it has
exited (or if no process is attached). Use it as the loop condition while
draining output.
use plugin procstream::{Proc}
let p = Proc.spawn("sleep", ["1"])
while p.alive() {
for line in p.read_lines() { print(line) }
}
print("done")
Return the process exit code, or -1 if not yet exited
Returns the process's exit code once it has finished. While the process is still
running (or if the code is unavailable) it returns -1, so check alive first or
only read the code after the loop ends.
use plugin procstream::{Proc}
let p = Proc.spawn("false", [])
while p.alive() {
for line in p.read_lines() { print(line) }
}
print("exited with {p.exit_code()}")
Terminate the child process
Terminates the child process and closes its stdin so it sees EOF. Use it to stop a long-running or interactive tool early; the handle is also cleaned up automatically when it is garbage-collected.
use plugin procstream::{Proc}
let p = Proc.spawn("yes", [])
for line in p.read_lines() { print(line) }
p.kill()
print("stopped, alive: {p.alive()}")