Options and Pipes
Process options define how a child process is started. They control the working directory, environment inheritance, executable lookup, detach mode, and the behavior of stdin, stdout, and stderr.
In vix::process, these options are part of Command. That keeps the launch configuration close to the program and its arguments. A command can be read as one complete description of the child process the application wants to start.
Header
Use the public process header:
#include <vix/process.hpp>For examples that print output or diagnostics:
#include <vix/print.hpp>ProcessOptions
ProcessOptions stores the execution settings used by Command.
struct ProcessOptions
{
PipeMode stdin_mode{PipeMode::Inherit};
PipeMode stdout_mode{PipeMode::Inherit};
PipeMode stderr_mode{PipeMode::Inherit};
bool search_in_path{true};
bool detach{false};
bool inherit_environment{true};
std::string working_directory{};
};2
3
4
5
6
7
8
9
10
11
12
The default behavior is conservative: the child inherits the parent streams, the executable may be found through PATH, the parent environment is inherited, the process is not detached, and an empty working directory means the child starts in the parent’s current directory.
Access options through Command
Most code does not create ProcessOptions directly. The usual workflow is to configure a Command through its builder methods.
vix::process::Command command("my-tool");
command.arg("--check");
command.cwd("./project");
command.search_in_path(true);
command.inherit_environment(true);
command.stdin_mode(vix::process::PipeMode::Null);
command.stdout_mode(vix::process::PipeMode::Pipe);
command.stderr_mode(vix::process::PipeMode::Pipe);2
3
4
5
6
7
8
9
The configured options can be inspected through command.options().
const auto &options = command.options();
vix::print("search in path", options.search_in_path);
vix::print("inherit environment", options.inherit_environment);
vix::print("working directory", options.working_directory);2
3
4
5
This is useful in tests, diagnostics, and higher-level wrappers that build commands before executing them.
PipeMode
PipeMode defines how a standard stream is handled.
enum class PipeMode
{
Inherit,
Pipe,
Null
};2
3
4
5
6
| Mode | Meaning |
|---|---|
PipeMode::Inherit | Use the parent process stream. |
PipeMode::Pipe | Create a pipe for the stream. |
PipeMode::Null | Redirect the stream to the platform null device. |
On POSIX, null redirection uses /dev/null. On Windows, it uses NUL.
Default stream behavior
By default, all three standard streams are inherited.
vix::process::Command command("my-tool");
const auto &options = command.options();
vix::print("stdin inherited", options.stdin_mode == vix::process::PipeMode::Inherit);
vix::print("stdout inherited", options.stdout_mode == vix::process::PipeMode::Inherit);
vix::print("stderr inherited", options.stderr_mode == vix::process::PipeMode::Inherit);2
3
4
5
6
7
Inherited streams are useful when the child should behave like it was launched from the same terminal as the parent process. Output appears directly in the parent console, and input can come from the same stdin.
Redirect stdin to null
A child process that should not read from the parent can receive null stdin.
vix::process::Command command("my-tool");
command.stdin_mode(vix::process::PipeMode::Null);2
3
This is a good default for commands launched by tools or services when the child should not wait for interactive input. The high-level output() API uses this kind of behavior internally because it runs a command to completion and captures the result without expecting interactive input.
Capture stdout and stderr
For normal output capture, use output().
#include <vix/print.hpp>
#include <vix/process.hpp>
int main()
{
#if defined(_WIN32)
vix::process::Command command("cmd");
command.arg("/C");
command.arg("echo stdout text & echo stderr text 1>&2");
#else
vix::process::Command command("sh");
command.arg("-c");
command.arg("echo stdout text; echo stderr text 1>&2");
#endif
auto result = vix::process::output(command);
if (!result)
{
vix::print("output failed", result.error().message());
return 1;
}
const auto &out = result.value();
vix::print("exit code", out.exit_code);
vix::print("stdout", out.stdout_text);
vix::print("stderr", out.stderr_text);
return out.success() ? 0 : 1;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
output() is the public convenience API for capturing both streams. It configures the process for capture, waits for completion, and returns a ProcessOutput value.
You can still set stream modes manually on a command, but the current public high-level API for reading captured text is output().
Manual pipe configuration
A command can explicitly request pipes for stdout or stderr.
vix::process::Command command("my-tool");
command.stdout_mode(vix::process::PipeMode::Pipe);
command.stderr_mode(vix::process::PipeMode::Pipe);2
3
4
This records the intended stream behavior on the command. The platform backend uses that information when spawning the child. For application code that simply wants the captured text, output() remains the clearest API because it performs the full spawn, capture, wait, and result-building workflow.
Discard stdout or stderr
Use PipeMode::Null when a stream should be discarded.
vix::process::Command command("my-tool");
command.stdout_mode(vix::process::PipeMode::Null);
command.stderr_mode(vix::process::PipeMode::Null);2
3
4
This is useful for commands where the exit code matters but the text output does not. It can also keep background tools from writing into the parent console.
Working directory
Use cwd() to set the child process working directory.
vix::process::Command command("git");
command.arg("status");
command.cwd("/path/to/project");2
3
4
The working directory is applied to the child process when it is launched. An empty working directory means the child uses the current working directory of the parent.
Keeping the working directory inside the command is useful because the parent process does not need to change its own working directory before launching a child.
Environment inheritance
By default, a child inherits the parent environment.
vix::process::Command command("my-tool");
command.inherit_environment(true);2
3
Environment overrides can be added with env().
command.env("APP_ENV", "test");
command.env("VIX_LOG_LEVEL", "debug");2
When inheritance is enabled, these overrides are merged with the parent environment. If a variable already exists, the command value replaces it for the child process.
When inheritance is disabled, the command uses only the environment values explicitly provided through the command.
vix::process::Command command("my-tool");
command.inherit_environment(false);
command.env("APP_ENV", "test");2
3
4
This is useful for tests or controlled subprocesses, but it should be used carefully. Many programs depend on environment variables such as PATH, HOME, USERPROFILE, locale settings, or runtime configuration.
Environment example
This example sets an environment variable and reads it from the child process.
#include <vix/print.hpp>
#include <vix/process.hpp>
int main()
{
#if defined(_WIN32)
vix::process::Command command("cmd");
command.arg("/C");
command.arg("echo %VIX_PROCESS_MODE%");
#else
vix::process::Command command("sh");
command.arg("-c");
command.arg("printf '%s\n' \"$VIX_PROCESS_MODE\"");
#endif
command.env("VIX_PROCESS_MODE", "options-example");
command.inherit_environment(true);
auto result = vix::process::output(command);
if (!result)
{
vix::print("output failed", result.error().message());
return 1;
}
vix::print("stdout", result.value().stdout_text);
return result.value().success() ? 0 : 1;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
The example names a shell explicitly because environment expansion is shell syntax. The process module passes arguments to the program; it does not perform shell expansion itself.
Executable lookup
search_in_path() controls whether the process backend may look for the executable in PATH.
vix::process::Command command("git");
command.search_in_path(true);2
3
This is the default behavior and is convenient for common tools such as git, cmake, python, or platform commands.
Disable path search when the application must run a specific executable.
vix::process::Command command("/usr/bin/git");
command.search_in_path(false);2
3
This makes the command more explicit. It is useful for tools that must avoid accidentally running another executable with the same name earlier in PATH.
Detached processes
detach() controls whether a child should run independently.
vix::process::Command command("my-daemon");
command.detach(true);2
3
Detached processes are different from normal short-lived child processes. The parent should not treat them as commands that will always be waited on immediately. Platform behavior also differs, so detach mode should be used only when the application intentionally wants the child to continue independently.
For most command execution workflows, keep detach(false) and use wait() or output() to observe completion.
Options with spawn
This example configures a command, spawns it, then waits for the child.
#include <vix/print.hpp>
#include <vix/process.hpp>
int main()
{
#if defined(_WIN32)
vix::process::Command command("cmd");
command.arg("/C");
command.arg("exit 0");
#else
vix::process::Command command("true");
#endif
command.stdin_mode(vix::process::PipeMode::Null);
command.stdout_mode(vix::process::PipeMode::Null);
command.stderr_mode(vix::process::PipeMode::Null);
command.search_in_path(true);
command.inherit_environment(true);
command.cwd(".");
auto spawned = vix::process::spawn(command);
if (!spawned)
{
vix::print("spawn failed", spawned.error().message());
return 1;
}
auto waited = vix::process::wait(spawned.value());
if (!waited)
{
vix::print("wait failed", waited.error().message());
return 1;
}
vix::print("exit code", waited.value());
return waited.value() == 0 ? 0 : 1;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
This pattern is useful when the application needs a Child handle but does not need to capture output.
Pipes and pipelines
The pipeline API uses pipes internally to connect two commands.
vix::process::Command first("echo");
first.arg("hello");
vix::process::Command second("cat");
auto spawned = vix::process::pipeline::spawn(first, second);2
3
4
5
6
The first command’s stdout is connected to the second command’s stdin. The application receives PipelineChildren, then waits for both processes with pipeline::wait().
This is the structured process API for a two-stage pipeline. It is different from writing a shell string such as "echo hello | cat". The module creates the pipe between the two child processes directly.
Common option patterns
For a short command where text output matters, prefer output().
auto result = vix::process::output(command);For a command where only the exit code matters, spawn and wait.
auto child = vix::process::spawn(command);
if (child)
{
auto code = vix::process::wait(child.value());
}2
3
4
5
6
For a command that should not read from the parent terminal, set stdin to null.
command.stdin_mode(vix::process::PipeMode::Null);For a command that should not write into the parent terminal, set stdout and stderr to null.
command.stdout_mode(vix::process::PipeMode::Null);
command.stderr_mode(vix::process::PipeMode::Null);2
For a command that needs shell syntax, run the shell explicitly.
#if defined(_WIN32)
vix::process::Command command("cmd");
command.arg("/C");
command.arg("echo hello & echo done");
#else
vix::process::Command command("sh");
command.arg("-c");
command.arg("echo hello && echo done");
#endif2
3
4
5
6
7
8
9
Common mistakes
Do not assume PipeMode::Pipe means your application automatically receives public read and write methods on Child. For captured text, use output(). For two-stage command composition, use the pipeline API.
Do not use shell syntax unless the shell is the program. Redirection, pipes written as |, variable expansion, wildcard expansion, and command chaining are shell features.
Do not disable environment inheritance without providing the variables the child needs. A minimal environment can be useful for tests, but it can also break tools that rely on PATH or platform-specific variables.
Do not use detach mode for normal command execution. Detached children are intentionally independent, while most process workflows should be observed with wait() or output().
Next step
Continue with spawn and child to understand how spawn() creates a Child handle and how that handle is used by status(), wait(), terminate(), and kill().
[Spawn and Child](./spawn-and-child.md)