Cmd

Cmd :: # (opaque)

Build and run child processes with native-safe programs, arguments, and environment values.

Use run! when exit status and output are data your application handles:

output = Cmd.new("roc")
	.arg("version")
	.timeout_ms(5_000)
	.run!()?

match output.status {
	Exited(0) => Stdout.write_bytes!(output.stdout_bytes)
	Exited(code) => Err(CommandFailed(code))
	Signaled(signal) => Err(CommandSignaled(signal))
}
exec! : OsStr, List(OsStr) => Try({}, [ExecFailed({ command : Str, exit_code : I32 }), FailedToGetExitCode({ command : Str, err : IOErr })])

Simplest way to execute a command by name with arguments. Stdin, stdout, and stderr are inherited from the parent process.

If you want to capture the output, use [exec_output!] instead.

Cmd.exec!("echo", ["hello world"])?
exec_cmd! : Cmd => Try({}, [ExecCmdFailed({ command : Str, exit_code : I32 }), FailedToGetExitCode({ command : Str, err : IOErr })])

Execute a Cmd (using the builder pattern). Stdin, stdout, and stderr are inherited from the parent process.

You should prefer using [exec!] instead, only use this if you want to use env, envs or clear_envs. If you want to capture the output, use [exec_output!] instead.

Cmd.new("cargo")
    .arg("build")
    .env("RUST_BACKTRACE", "1")
    .exec_cmd!()?
exec_output! : Cmd => Try({ stdout_utf8 : Str, stderr_utf8_lossy : Str }, [StdoutContainsInvalidUtf8({ cmd_str : Str, err : [BadUtf8({ problem : _, index : U64 })] }), NonZeroExitCode({ command : Str, exit_code : I32, stdout_utf8_lossy : Str, stderr_utf8_lossy : Str }), FailedToGetExitCode({ command : Str, err : IOErr })])

Execute command and capture stdout and stderr as UTF-8 strings. Invalid UTF-8 sequences are replaced with the Unicode replacement character.

Use [exec_output_bytes!] instead if you want to capture the output in the original form as bytes. [exec_output_bytes!] may also be used for maximum performance, because you may be able to avoid unnecessary UTF-8 conversions.

cmd_output =
    Cmd.new("echo")
        .args(["Hi"])
        .exec_output!()?

Stdout.line!("Echo output: ${cmd_output.stdout_utf8}")?
exec_output_bytes! : Cmd => Try({ stderr_bytes : List(U8), stdout_bytes : List(U8) }, [NonZeroExitCodeB({ exit_code : I32, stdout_bytes : List(U8), stderr_bytes : List(U8) }), FailedToGetExitCodeB(IOErr)])

Execute command and capture stdout and stderr in the original form as bytes.

Use [exec_output!] instead if you want to get the output as UTF-8 strings.

cmd_output =
    Cmd.new("echo")
        .args(["Hi"])
        .exec_output_bytes!()?

Stdout.line!("${Str.inspect(cmd_output)}")? # {stderr_bytes: [], stdout_bytes: [72, 105, 10]}
exec_exit_code! : Cmd => Try(I32, [FailedToGetExitCode({ command : Str, err : IOErr })])

Execute a command and return its exit code. Stdin, stdout, and stderr are inherited from the parent process.

You should prefer using [exec!] or [exec_cmd!] instead, only use this if you want to take a specific action based on a specific non-zero exit code. For example, roc check returns exit code 1 if there are errors, and exit code 2 if there are only warnings. So, you could use exec_exit_code! to ignore warnings on roc check.

exit_code = Cmd.new("cat").arg("non_existent.txt").exec_exit_code!()?
new : OsStr -> Cmd

Create a new command with the given program name. Use a function that starts with exec_ to execute it.

cmd = Cmd.new("ls")
new_str : Str -> Cmd

Create a new command from a dynamic Str. String literals can be passed directly to new.

cwd : Cmd, Path -> Cmd

Set the child working directory without changing the parent directory. Use an absolute executable path or a bare PATH name: resolving a relative executable against cwd is platform-specific.

stdin : Cmd, [Default, Inherit, Null, Bytes(List(U8)), Pipe] -> Cmd

Bytes supplies and closes stdin automatically. Pipe supports Child.write!. Default is Null for run!/exec_output!, Inherit for spawn!/exec_cmd!.

stdout : Cmd, [Default, Inherit, Null, Capture, Pipe, Tee] -> Cmd

Capture retains bytes; Pipe queues tagged Child.read! events; Tee captures and forwards to the parent. Default captures for run!/exec_output!, and inherits for spawn!/exec_cmd!. Tee exposes a pipe, not a terminal, to the child.

stderr : Cmd, [Default, Inherit, Null, Capture, Pipe, Tee] -> Cmd

Configure stderr independently, with the same modes and defaults as stdout.

timeout_ms : Cmd, U64 -> Cmd

Zero disables the execution deadline. It covers output draining too.

output_limit : Cmd, U64 -> Cmd

Combined capture budget in bytes (default 16 MiB). Exceeding it cancels the command and returns OutputLimit with the retained partial output.

pending_limit : Cmd, U64 -> Cmd

Combined unread Pipe event budget (default 1 MiB). Consume events with Child.read! while running; exceeding this budget cancels the child.

manage_tree : Cmd, Bool -> Cmd

Also terminate descendants on cancellation, using a Unix process group or Windows Job Object. Disabled by default; descendants must not escape it.

merge_stderr : Cmd, Bool -> Cmd

Send both child streams into one OS pipe using stdout's mode and budget. This preserves kernel write order; separate streams have no total ordering.

run! : Cmd => Try(RunOutput, RunErr)

Nonzero exits and signal termination are returned as status data. Defaults to null stdin and captured stdout/stderr. Successful completion waits for output EOF; deadlines include draining and tee forwarding.

spawn! : Cmd => Try(Child, IOErr)

Start a managed child immediately. Default streams are inherited.

arg : Cmd, OsStr -> Cmd

Add a single argument to the command. ❗ Shell features like variable substitution (e.g. $FOO), glob patterns (e.g. *.txt), ... are not available.

cmd = Cmd.new("ls").arg("-l")
arg_str : Cmd, Str -> Cmd

Add a dynamic Str argument. String literals can be passed directly to arg.

args : Cmd, List(OsStr) -> Cmd

Add multiple arguments to the command. ❗ Shell features like variable substitution (e.g. $FOO), glob patterns (e.g. *.txt), ... are not available.

cmd = Cmd.new("ls").args(["-l", "-a"])
args_str : Cmd, List(Str) -> Cmd

Add multiple dynamic Str arguments. Lists of string literals can be passed directly to args.

env : Cmd, OsStr, OsStr -> Cmd

Add a single environment variable to the command.

cmd = Cmd.new("env").env("FOO", "bar") # add the environment variable "FOO" with value "bar"
env_str : Cmd, Str, Str -> Cmd

Add a dynamic Str environment variable. String literals can be passed directly to env.

envs : Cmd, List((OsStr, OsStr)) -> Cmd

Add multiple environment variables to the command.

cmd = Cmd.new("env").envs([("FOO", "bar"), ("BAZ", "qux")])
envs_str : Cmd, List((Str, Str)) -> Cmd

Add multiple dynamic Str environment variables. Lists of literal pairs can be passed directly to envs.

clear_envs : Cmd -> Cmd

Clear all environment variables before running the command. Only environment variables added via env or envs will be available. Useful if you want a clean command run that does not behave unexpectedly if the user has some env var set.

cmd =
    Cmd.new("env")
        .clear_envs()
        .env("ONLY_THIS", "visible")
check_available! : Str => Bool

Report whether command can be found on the system as something runnable.

A bare name (like "git") is looked up across the PATH entries; a name containing a path separator is checked as-is. On Windows the candidate extensions come from %PATHEXT%.

On Unix this checks for an executable bit; a directory is not reported even though it carries one, though a symbolic link to a directory is a rare exception.

to_str : Cmd -> Str

Render a command configuration as a stable, escaped string.

to_inspect : Cmd -> Str

Customize command output for Str.inspect.

RunOutput : { status : [Exited(I32), Signaled(I32)], stdout_bytes : List(U8), stderr_bytes : List(U8) }

The completed child status and captured output. Output is empty for streams configured as Inherit or Null.

PartialOutput : { stdout_bytes : List(U8), stderr_bytes : List(U8) }

Output retained before a timeout or capture limit stopped the command.

RunErr : [IO(IOErr), Timeout(PartialOutput), OutputLimit(PartialOutput)]

A failure to start or supervise a command, an expired deadline, or a full capture budget. Nonzero exit codes are represented by RunOutput.status.

Child

Cmd.Child :: # (opaque)

A managed child. Final reference release terminates and reaps the process. Use close! for deterministic cleanup before the last reference is released.

pid! : Child => Try(U32, IOErr)

Return the operating-system process identifier while the child is open.

wait! : Child => Try(RunOutput, RunErr)

Closes piped stdin before waiting; call read!/write! for an interactive exchange first.

try_wait! : Child => Try(List(RunOutput), RunErr)

Returns [] while running, or one result after exit and output draining.

kill! : Child => Try({}, IOErr)

Request forced termination. Use wait! to observe termination and reaping.

close! : Child => Try({}, IOErr)

Terminate and reap, invalidating every alias. Repeated close! succeeds.

close_stdin! : Child => Try({}, IOErr)

Close piped stdin so the child observes EOF. Repeated closes succeed.

write! : Child, List(U8), U64 => Try({}, IOErr)

Write piped stdin within timeout milliseconds. A timed-out write may have delivered a prefix; retrying the whole input can duplicate bytes.

read! : Child, U64, U64 => Try([Stdout(List(U8)), Stderr(List(U8)), End], IOErr)

Returns one stream chunk, or End after all output drains. Timeout is an IO error.