std/process
import std/process to use it. Source: src/std/process.iyi at v0.18.0.
import std/process::{Process} done = Process.run("git", ["rev-parse", "HEAD"]).or_panic
puts done.stdout if done.success?run starts the program, hands it input on its standard input (or nothing: the null device), collects everything it writes to standard output and standard error, waits for it to end, and answers a ProcessResult: the exit code and the two texts. A program that ran and failed is a result, not an error - its exit code says so, success? reads it. A program that could not be started - no such program, not allowed to run it, no such directory to run it in - is a ProcessError, the one error member, and a cancelled task answers Cancelled (III.4.2) after the child is ended: a task that stops leaves no program running.
capture: false gives the child this program's own standard streams instead, for a program whose output is for the person at the terminal; its result carries the exit code and two empty texts.
spawn starts the program and answers at once with a ChildProcess, for a child this program talks to while it runs:
child = Process.spawn("sort", input: Redirect::Pipe, output: Redirect::Pipe).or_panic
child.stdin.write("pear\napple\n")
child.stdin.close
while line = child.stdout.read_line.or_panic
puts line
end
child.waitEach of its three streams is a Redirect: a Pipe the handle writes or reads, this program's own (Inherit, the default), or the null device. The handle has the child's pid; wait for its ProcessStatus, ended? to ask without waiting, terminate, kill and signal to end it. A child is this program's to wait for. A task cancelled in any of the handle's calls ends the child first, as run does.
exec replaces this program with another, keeping its pid and its three streams, and answers only if it could not; Windows has no such call, and there the program runs and this one ends with its exit code.
The child's environment is this program's, and env: changes it for the child alone: each name given is set to its value, and a name given nil is removed. This program's own environment is untouched, so a task that runs a child with a variable changed does not change it under the other tasks. On Windows a name is matched as Windows matches one, without regard to case. The program itself is still looked for on this program's own PATH.
The arguments are the program's argv, one string each, and no shell reads them: a space, a quote, a $ or a ; in one is that character in the child's argument. The program is found the way a shell finds it - a name with a separator in it is a path, relative to this program's directory, and a bare name is looked for on PATH - and on Windows a bare name without an extension is looked for as NAME.exe and NAME.com. A .bat or .cmd is refused rather than run: Windows runs one through cmd.exe, which reads the command line again with rules of its own, so an argument could run a command; ask for cmd.exe by name and quote for it yourself.
Nothing blocks the thread. The calling fiber and two siblings of its own read the child's output, write its input and wait for its end on the poller, so a program running a child for a minute keeps serving its other tasks for that minute:
* Linux: `clone` and `execve` by syscall, the child's end of each pipe
put in place between the two, and the end awaited through a pidfd
(`pidfd_open`, Linux 5.3); on an older kernel the fiber asks
`wait4` with WNOHANG and sleeps between asks. A write to a child that
stopped reading answers EPIPE and not SIGPIPE: the signal is held on
the writing thread for the write and taken off it after.
* darwin: libSystem's `posix_spawn`, and the end awaited as a kqueue of
its own, one whose single event is the child's NOTE_EXIT, which the
poller waits on like any descriptor.
* Windows: `CreateProcessW` with the child's three handles and no
others (PROC_THREAD_ATTRIBUTE_HANDLE_LIST), overlapped named pipes on
this side so the reads and writes park on the completion port, and
the end posted to the port by `RegisterWaitForSingleObject`.Process.executable is this program's own path, which is how a program starts another copy of itself, and Process.pid its process id.
ChildInputclass
The child's standard input, as a pipe this program writes.
pub class ChildInput
# Closes the pipe: the child reads the end of its input.
def close : Nil
def initialize(child : ProcessChild)
# Writes all of *text*, parking while the pipe is full. A child that
# closed its input, or ended, is `ChildInputClosed`; `close` has made it
# so too.
def write(text : String) : Nil | ChildInputClosed | Cancelled
endChildInputClosedstruct
A write to a child that had closed its input - or ended.
pub struct ChildInputClosed
def initialize(pid : Int64)
def pid : Int64
endChildOutputclass
One of the child's output streams, as a pipe this program reads. Each read parks until the child has written, or its end of the pipe closed.
pub class ChildOutput
def initialize(child : ProcessChild, output : ::Bool)
# What the child has written that this program has not read, up to
# *max* bytes, once there is some: nil at the end.
def read(max : Int32 = 65536) : String | ::Nil | Cancelled
# Everything the child writes from here to its end.
def read_all : String | Cancelled
# The next line, without its `\n` (and a `\r` before it) unless *chomp*
# is false; the last one need not end in one. nil at the end.
def read_line(chomp : ::Bool = true) : String | ::Nil | Cancelled
endChildProcessclass
A child started by Process.spawn, running beside this program.
pub class ChildProcess
# Whether the child has ended, without waiting; once it has, `status`
# says how.
def ended? : ::Bool
def initialize(child : ProcessChild, input : ::Bool, output : ::Bool, error : ::Bool)
# Ends the child: SIGKILL on Linux and darwin, and on Windows the child
# and every program it started. Nothing once it has ended.
def kill : Nil
# The child's process id, which the operating system may give another
# process once this one has ended and been waited for.
def pid : Int64
# Sends *number* to the child, Linux and darwin. Windows has no signals,
# and a call there panics: `terminate` and `kill` are what it has.
def signal(number : Int32) : Nil
# How the child ended, once it has; nil while it runs.
def status : ProcessStatus | ::Nil
# The child's standard error, asked for as `Redirect::Pipe`.
def stderr : ChildOutput
# The child's standard input, asked for as `Redirect::Pipe`.
def stdin : ChildInput
# The child's standard output, asked for as `Redirect::Pipe`.
def stdout : ChildOutput
# Asks the child to stop: SIGTERM on Linux and darwin, which it may
# handle. Windows has no request to stop a program, and ends it and what
# it started, as `kill` does. Nothing once it has ended.
def terminate : Nil
# Parks until the child has ended and answers how; at once if it had.
# Its input, if a pipe, is closed first: a child reading to the end of
# its input would otherwise wait for this program while this program
# waits for it.
def wait : ProcessStatus | Cancelled
endProcessstruct
pub struct Process
# Replaces this program with *command*, and answers only if it could not:
# a `ProcessError`, with this program as it was, in the directory it was.
#
# On Linux and darwin the process becomes the program - the same pid,
# this program's three standard streams and none of its other
# descriptors (on Linux from 5.11, which marks them for the exec) - and
# nothing after the call runs. Windows cannot replace a program: there
# the program runs on this program's streams, this program waits for it
# and ends with its exit code, and a pid is not kept.
#
# *command*, *args*, *chdir* and *env* are `run`'s. What this program had
# written to its own buffered streams is flushed first.
def self.exec(command : String, args : Array(String) = [] of String, chdir : String | ::Nil = nil, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ProcessError
# This program's own executable, as a path; nil where the platform will
# not say.
def self.executable : String | ::Nil
def initialize
# This program's own process id.
def self.pid : Int64
# Runs *command* with *args* and answers once it has ended.
#
# *command* is a path, or a name looked for on `PATH` (on Windows also as
# `NAME.exe` and `NAME.com`; a `.bat` or `.cmd` is refused). Each of
# *args* reaches the child as one argument, and no shell reads them.
# *input* is written to the child's standard input, and without it the
# child reads nothing. *chdir* is the directory it runs in. *env* changes
# the child's environment alone: a name given a value is set, a name
# given `nil` removed. With *capture* false the child writes to this
# program's own streams and the result's texts are empty.
#
# Answers a `ProcessResult` for a child that ran, whatever its exit code;
# a `ProcessError` when it could not be started; and `Cancelled`, after
# the child is ended, when the task running this is cancelled. Nothing
# blocks the thread: the other tasks run while the child does.
def self.run(command : String, args : Array(String) = [] of String, input : String | ::Nil = nil, chdir : String | ::Nil = nil, capture : ::Bool = true, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ProcessResult | ProcessError | Cancelled
# Starts *command* and answers at once with the running child, which
# this program then talks to and waits for: `ChildProcess`.
#
# *command*, *args*, *chdir* and *env* are `run`'s. Each of *input*,
# *output* and *error* is a `Redirect`: `Pipe` to write the child's input
# or read its output through the handle, `Inherit` to share this
# program's own stream (which is flushed first), `Null` for the null
# device. All three are inherited unless asked.
#
# Answers a `ProcessError` when the child could not be started.
#
# The child runs until it ends or is ended; `wait` collects its end and
# is what a program owes every child it starts. A pipe nobody reads fills
# and stops the child that writes it, so read what is asked for as a
# pipe - on two tasks at once for both output and errors - before or
# while waiting. A task cancelled in any of the handle's calls ends the
# child before it answers `Cancelled`, as `run` does: a task that stops
# leaves no program running.
def self.spawn(command : String, args : Array(String) = [] of String, input : Redirect = Redirect::Inherit, output : Redirect = Redirect::Inherit, error : Redirect = Redirect::Inherit, chdir : String | ::Nil = nil, env : Hash(String, String) | Hash(String, String | ::Nil) | Hash(String, Nil) | Nil = nil) : ChildProcess | ProcessError
endProcessErrorstruct
A program that could not be started, and the platform's error number for why. directory is set when it was the directory it was to run in.
pub struct ProcessError
def code : Int32
def command : String
def denied? : ::Bool
def directory : String | ::Nil
def initialize(command : String, code : Int32, directory : String | ::Nil = nil)
# Nothing by that name: not on PATH, no such file, no such directory.
def not_found? : ::Bool
def reason : String
endProcessResultstruct
What a program that ran came back with. exit_code is the child's own; on Linux and darwin a child ended by a signal has signal set and the exit code a shell would report for it, 128 plus the signal. On Windows signal is always 0 and a crash is its exception code, 0xC0000005 for an access violation, read as an Int32.
pub struct ProcessResult
def exit_code : Int32
def initialize(exit_code : Int32, signal : Int32, stdout : String, stderr : String)
def signal : Int32
def stderr : String
def stdout : String
def success? : ::Bool
endProcessStatusstruct
How a child ended: exit_code, and on Linux and darwin the signal that ended it, as ProcessResult says them.
pub struct ProcessStatus
def exit_code : Int32
def initialize(exit_code : Int32, signal : Int32)
def signal : Int32
def success? : ::Bool
endRedirectenum
How one of a spawned child's standard streams is connected.
pub enum Redirect : Int32
Pipe = 0
Inherit = 1
Null = 2
endError for ProcessErrorimpl
impl Error for ProcessError
def message : String
endError for ChildInputClosedimpl
impl Error for ChildInputClosed
def message : String
end
Processes: run another program, and read what it said.