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 or index it with a string name or path. Indexing returns a
Program proxy, which 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, term.output(). Use stdin:, stdout:, and
stderr: to replace those connections for one invocation.
Each redirect accepts a path, an io.Io stream, 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. The
program starts at the file's position, and when run returns, the file is
still open and positioned where the program left off:
open build.log w do |log|
log.write "configure\n"
run ./configure stdout: $log stderr: :STDOUT:
log.write "done\n"
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 chunks = []
run gzip -c data.txt stdout: $chunks mode: :CHUNK:
In the first example, lines receives one Str per line. In the second,
chunks receives Bin chunks of the compressed output. The
mode: argument chooses between these forms. To collect the
exact bytes in one piece, redirect to a BinBuf, which is an
io.Io stream.
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
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 lines = pipeline
do run 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:.
A redirect to an io.Io stream skips the translation: bytes are
copied between the program and the stream as they are, whatever the mode:.
A stream that is also iterable, such as io.Buffered, is read
as bytes rather than items, and the copy may read past what the program
consumed. When the program succeeds, an error from the stream fails the
invocation; otherwise the program's exit status is reported.
File handles and the shell.stdin, shell.stdout and shell.stderr
singletons go further and hand the program the underlying stream, so a file's
position afterward is exactly where the program left it.
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: