Read the environment and run other programs
std.env reads the variables a program was started with. std.process starts
another program from a list of arguments and collects what it prints:
import std.bytes
import std.io
import std.env
import std.process
from std.process import Stdio
def main() -> Result[(), AllocError | IoError | std.bytes.BytesError | std.io.StreamError]:
print(std.env.get("PLENTY_LESSON_UNSET")?)?
mut command = std.process.command("printf")?
command.arg("%s\n")?
command.arg("two words; echo $HOME | wc")?
command.stdout(Stdio.Piped)
mut child = command.spawn()?
output = child.output(4096)?
print(output.stdout.to_text()?)?
print(output.status.success(), output.status.code())?
Ok(())
Option[str].Nothing
two words; echo $HOME | wc
True Option[i32].Some(0)
An unset variable is Nothing, not an error. Each arg is one argument,
delivered byte for byte: the semicolon, the $HOME, and the | are printed,
not run, because no shell reads them. Stdio.Piped connects the child’s output
to a pipe, and output collects at most 4096 bytes of it and waits for the
child to end.
If a configuration supplies arguments as one string, use std.shlex.split
to recognize quoted words, then pass them individually to arg. Do not quote
arguments passed to arg itself; those quotes would become literal data.
import std.shlex
from std.shlex import ShlexError
def main() -> Result[(), ShlexError | AllocError | IoError]:
arguments = std.shlex.split("--title 'Two words' --literal '$HOME'")?
for argument in &arguments:
print(argument)?
Ok(())
--title
Two words
--literal
$HOME
split removes word quoting without expanding $HOME or interpreting pipes.
std.shlex also provides quote for the cases
where one argument must be written into a POSIX shell command string.
To find where to keep settings or caches, ask
std.fs.dirs rather than reading HOME
yourself: config_dir() and cache_dir() apply the XDG rules and return a
Path, or Nothing when the environment names no directory.
When shell syntax is what you want, ask for it by name. shell runs
/bin/sh -c with your script. communicate also feeds the child’s input:
import std.io
import std.bytes
import std.process
from std.process import Stdio
def main() -> Result[(), AllocError | IoError | std.io.StreamError | std.bytes.BytesError]:
mut command = std.process.shell("tr a-z A-Z; echo \"mode: $MODE\" >&2; exit 3")?
command.env("MODE", "loud")?
command.stdin(Stdio.Piped)
command.stdout(Stdio.Piped)
command.stderr(Stdio.Piped)
mut child = command.spawn()?
output = child.communicate(b"quiet words", 4096)?
print(output.stdout.to_text()?)?
print(output.stderr.to_text()?)?
print(output.status.success(), output.status.code())?
Ok(())
QUIET WORDS
mode: loud
False Option[i32].Some(3)
env sets a variable for this child only. communicate writes the input,
reads both outputs as they arrive so that no pipe fills up, and returns the
exit status. A nonzero exit code is a normal result: the program ran. Never
put text you did not write into a shell script; pass it as an argument or a
variable.
A program that cannot be started is an error, and a child that runs too long can be stopped:
import std.time
import std.process
def main() -> Result[(), AllocError | IoError | std.process.WaitError]:
mut missing = std.process.command("plenty-no-such-program")?
match missing.spawn():
case Ok(_):
print("started")?
case Err(error):
print(error)?
mut sleeper = std.process.command("sleep")?
sleeper.arg("60")?
mut child = sleeper.spawn()?
cancel = CancellationToken()?
match child.wait_timeout(&std.time.milliseconds(50u32), &cancel)?:
case Some(_):
print("ended")?
case Nothing:
print("still running")?
child.kill()?
status = child.wait()?
print(status.success(), status.code(), status.signal())?
Ok(())
IoError.System(2)
still running
False Option[i32].Nothing Option[i32].Some(9)
System(2) is the operating system’s “no such file”. wait_timeout gives up
after 50 milliseconds and leaves the child running. kill stops it, and wait
collects its status: ended by signal 9, with no exit code. Always wait for a
child you started, even after killing it; a dropped Child is not cleaned up.
See the references for std.env and
std.process, including pipes you read and
write yourself.
To inspect CPU, memory, disk or process information without launching a command, continue to system information.