External Programs
External programs are available through proc.run.
Programs inherit the current strand's shell context, including its
working directory, environment, and standard streams.
Program Lookup
Call run directly, index it with a name or path, or destructure it to bind
identifier-safe program names. The resulting Program
proxy is a function:
Alternatively, call run directly with a path or name:
Calling a program resolves its executable using the current VFS and PATH. Use
.which() to resolve it without running it. It
returns the executable's path, or nil if the program is not found:
Working Directory and Environment
Programs inherit the current strand's working directory and environment. Use
cd and
env to override them for a scoped block:
Redirecting I/O
Without explicit redirects, a program's stdin/stdout is connected to the
current strand's input Iter and output Sink, while stderr is connected to
the current console. Use stdin:, stdout:, and
stderr: to replace those connections for one invocation.
Each redirect accepts a path or an Iter/Sink.
std.null provides empty input and discards output:
import std
# Read and write files.
run sort stdin: unsorted.txt stdout: sorted.txt
# Supply values and collect output values.
let lines = []
run sort stdin: ["c\n", "a\n", "b\n"] stdout: $lines
# Discard a stream
run tool stdout: $std.null
# Merge stderr into stdout.
run tool stdout: combined.log stderr: :STDOUT:
An fs.File can be used directly as an input or output.
See Terminal Output for the distinction between the output stream and the console.
Capturing Output
Use sub when the result should be one
string. By default, it removes one trailing line ending from the completed
capture:
sub captures the strand output stream, not the console. A program's stderr
still goes to the console unless it is redirected. Merge stderr into stdout to
capture both:
Redirect stdout or stderr to a sink when the caller needs individual values, binary chunks, or a streaming consumer:
let lines = []
run git status --short stdout: $lines
let bytes = BinBuf()
run gzip -c data.txt stdout: $bytes mode: :CHUNK:
In the first example, lines receives one Str per line. In the second,
bytes receives Bin chunks and retains the exact compressed output. The
mode: argument chooses between these forms.
Use term.sub when the
goal is to capture any console-bound output, including a program's unredirected
stderr:
Pipelines
External programs integrate with
strand.pipeline
import
strand:
- pipeline
let :cat :grep :sort ... = run
pipeline
do cat messages.log
do grep ERROR
do sort
When two external program stages are adjacent, the pipeline connects their stdio streams directly. Bytes pass from the first program's stdout to the second program's stdin without becoming Do values.
Connections between stages use a
proc.PipeReceiver and
proc.PipeSender. The same pipe endpoints are the
implicit input and output of a
strand.stream strand; its Stream
handle exposes wrappers around them through iter() and sink(). Use
PipeReceiver.lines() or PipeReceiver.chunks() to choose how a Do stage
receives bytes from an external program.
A Do stage adjacent to an external program receives discrete values. In this
example, each receives Str values for each line from cat, and collect
returns an array:
import
strand:
- pipeline
- each
- collect
let :cat ... = run
let lines = pipeline
do cat messages.log
do each do |line| line.upper()
do collect()
The input: and output: arguments set the endpoints of the whole pipeline.
See Pipelines for built-in Do stages,
custom stages, cancellation, and error handling.
Termination
A nonzero exit status raises proc.Error. Canceling a
strand running a program terminates that program. Background programs launched
under strand.spawn or strand.stream are terminated as a process group on
Unix.
Use
proc.with_policy
to change termination defaults for a block, or policy: for one invocation:
Windows uses CTRL_BREAK_EVENT; signal: overrides apply only to Unix
targets.
External I/O and Do values
An external program reads and writes byte streams, while a Do pipeline stage
reads from an Iter and writes to a
Sink. At a boundary between the two, run translates
in each direction:
- For stdin,
runtakes values from the inputIterand writes each value's bytes to the program.Binvalues are written verbatim, while other types are coerced toStr. It adds no separator or line ending. - For stdout and stderr,
runreads bytes from the program, divides them into values, and puts those values into the outputSink.
This applies both to the implicit Iter and Sink in a pipeline and to values
given explicitly with stdin:, stdout:, or stderr:. Certain types pass the
underlying stream directly when possible to avoid the round-trip through a Do
value, such as File handles and the shell.stdin and shell.stdout
singletons.
Output Mode
The mode: argument controls how stdout or stderr bytes are divided into
values:
| Mode | Yields |
|---|---|
:LINE: |
one Str per line, line ending included |
:CHUNK: |
arbitrary-sized Bin values |
:LINE: is the default. A stream whose last line has no terminator simply
yields a final value without one. Concatenating the values from either mode
reproduces the program's output byte for byte.
Adding or removing a line ending is a separate, explicit step. Use
chomp and
crimp on an iterator, or
prechomp and
precrimp on a sink: