Subprocess Streaming

The proc module (1.1.0) spawns a child process and returns immediately, exposing stdout, stderr, and stdin as stream-shaped values (see streams.IOStream). Use it when you need to start a long-running process, pipe data in, or read output incrementally. For one-shot synchronous runs that return when the child exits, use process.run from the process module instead.

import io;
import proc;

let p = proc.spawn("echo", ["hello"]);
io.println(p.stdout.readAll());
let code = p.wait();
io.println("exit " + (code as string));

API

proc.spawn(command, args = [], opts = {}): Process

Starts the process and returns a Process value. The constructor is non-blocking; the child runs concurrently.

opts key Type Description
pty bool When true, attach a pseudo-terminal. stdin and stdout share the master fd; stderr is null.
cwd string Set the child's working directory.
env dict<string, string> Replace the child's environment.

The returned Process has:

Member Type Description
pid int The child's process id
handle int Opaque native handle
stdin streams.IOStream Write-only stream to the child's stdin
stdout streams.IOStream Read-only stream from the child's stdout
stderr streams.IOStream or null Read-only stream from stderr (null in PTY mode)
wait() int Block until exit; return the exit code
kill() void Send SIGKILL
signal(name) void Send the named signal ("SIGTERM", "SIGHUP", etc.)

Streaming stdin and stdout

Process.stdin is an IOStream. Use write / writeln and call close() to signal EOF to the child. stdout exposes the same surface; iterate with for (line in p.stdout) to consume line-by-line.

import io;
import proc;

let p = proc.spawn("grep", ["error"]);
p.stdin.writeln("log: ok");
p.stdin.writeln("log: error here");
p.stdin.close();
for (line in p.stdout) {
    io.println(line);
}
p.wait();

PTY mode

Some interactive programs detect a terminal and behave differently. {pty: true} attaches a pseudo-terminal so the child sees a TTY:

import io;
import proc;

let p = proc.spawn("python3", ["-i"], {"pty": true});
p.stdin.writeln("print(1 + 1)");
p.stdin.writeln("exit()");
io.println(p.stdout.readAll());
p.wait();

In PTY mode stderr merges into stdout, so p.stderr is null.

Signals and exit

process.kill() sends SIGKILL. process.signal(name) sends a named signal:

import sys;
import proc;

let p = proc.spawn("sleep", ["60"]);
sys.sleep(100);
p.signal("SIGTERM");
let code = p.wait();

The exit code from wait() mirrors the shell convention:

  • Normal exit: the program's exit code.
  • Killed by signal: 128 + signal number (e.g. SIGTERM is 143).

When to prefer process.run

The process module's process.run(cmd, args, opts) is the right choice when:

  • The child writes a bounded amount of output (no streaming).
  • You want to block until completion and inspect the result.
  • You don't need to pipe stdin.

proc.spawn is the right choice when:

  • The child runs concurrently with the parent.
  • You want to stream stdout / stderr incrementally.
  • You need to write to stdin and close it.
  • You need PTY mode for interactive programs.