Process

This struct is used to prepare configuration for process and run it.

The following methods of running a process are supported:

- execute: run process and catch its output and exit code. - spawn: spawn the process in background, and optionally pipe its output. - pipe: spawn the process and attach configurable pipes to catch output.

The configuration of a process can be done like so:

1. Create the Process instance specifying the program to run. 2. Apply your desired configuration (args, env, workDir) via calls to one of the corresponding methods. 3. Run one of execute, spawn or pipe methods, that will actually start the process.

Configuration methods come in two families with distinct semantics:

- set* / add* methods mutate the current instance in place and return void, making them suitable for conditional modification of an already-stored Process variable. - with* / in* methods return a new Process by value, leaving the original unchanged, making them safe to use in chained expressions. Most set* / add* method has a with* counterpart: addArgswithArgs, setWorkDirinWorkDir, setEnvwithEnv, etc.

Constructors

this
this(string program)
this(Path program)

Create new Process instance to run specified program.

Members

Functions

addArgs
void addArgs(string[] args)

Add arguments to the process.

copy
Process copy()

Copy the process configuration. Could be useful when needed to run command multipe times with slightly different configuration. Returns new instance of process.

execute
auto execute(size_t max_output)

Execute the configured process and capture output.

execv
void execv()

Replace current process by executing program as configured by Process instance.

inWorkDir
Process inWorkDir(string workdir)
Process inWorkDir(Path workdir)

Return a new Process with the working directory set to the provided path, leaving the original unchanged.

opBinary
Process opBinary(string arg)

Append a single argument, returning a new Process (non-mutating). Equivalent to withArgs.

opBinary
Process opBinary(string[] args)

Append multiple arguments, returning a new Process (non-mutating). Equivalent to withArgs.

opOpAssign
void opOpAssign(string arg)

Append a single argument in place (mutating). Equivalent to addArgs.

opOpAssign
void opOpAssign(string[] args)

Append multiple arguments in place (mutating). Equivalent to addArgs.

pipe
auto pipe(Redirect redirect)

Pipe process

setArgs
void setArgs(string[] args)

Set arguments for the process

setConfig
void setConfig(std.process.Config config)

Set process configuration

setEnv
void setEnv(string[string] env)

Set environemnt for the process to be started. Could be called multiple times to update environment.

setEnv
void setEnv(string key, string value)

Set environment variable (specified by key) to provided value

setFlag
void setFlag(std.process.Config.Flags flag)
void setFlag(std.process.Config flags)

Set configuration flag for process to be started

setGID
void setGID(gid_t gid)

Set GID to run process with

setNewEnv
void setNewEnv()

Run process with new environment (do not inherit environment variables from parent process)

setStderrPassThrough
void setStderrPassThrough()

Apply Config.stderrPassThrough flag. With this flag, stderr will not be captured, but instead directly passed to console or terminal.

setUID
void setUID(uid_t uid)

Set UID to run process with

setUser
void setUser(string username, bool userWorkDir)

Run process as specified user

setWorkDir
void setWorkDir(string workdir)
void setWorkDir(Path workdir)

Set work directory for the process to be started

spawn
auto spawn(File stdin, File stdout, File stderr)

Spawn process

toString
string toString()

Return string representation of process to be started

withArgs
Process withArgs(string[] args)

Return a new Process with the provided arguments appended, leaving the original unchanged.

withConfig
Process withConfig(std.process.Config config)

Return a new Process with the process configuration set to the provided value, leaving the original unchanged.

withEnv
Process withEnv(string[string] env)
Process withEnv(string key, string value)

Return a new Process with the environment updated with the provided key-value pairs, leaving the original unchanged.

withFlag
Process withFlag(std.process.Config.Flags flag)
Process withFlag(std.process.Config flags)

Return a new Process with the given configuration flag set, leaving the original unchanged.

withGID
Process withGID(gid_t gid)

Return a new Process configured to run with the given GID, leaving the original unchanged.

withNewEnv
Process withNewEnv()

Return a new Process configured to start with a fresh environment (not inheriting parent environment variables), leaving the original unchanged.

withStderrPassThrough
Process withStderrPassThrough()

Return a new Process with Config.stderrPassThrough set, leaving the original unchanged.

withUID
Process withUID(uid_t uid)

Return a new Process configured to run with the given UID, leaving the original unchanged.

withUser
Process withUser(string username, bool userWorkDir)

Return a new Process configured to run as the given user, leaving the original unchanged.

Examples

// It is possible to run process in following way:
auto result = Process("my-program")
        .withArgs("--verbose", "--help")
        .withEnv("MY_ENV_VAR", "MY_VALUE")
        .inWorkDir("my/working/directory")
        .execute()
        .ensureStatus!MyException("My error message on failure");
writeln(result.output);
// Also, in Posix system it is possible to run command as different user:
auto result = Process("my-program")
        .withUser("bob")
        .execute()
        .ensureStatus!MyException("My error message on failure");
writeln(result.output);

Test simple execution of the script

import std.string;
import std.ascii : newline;

import unit_threaded.assertions;

auto temp_root = createTempPath();
scope(exit) temp_root.remove();

version(Posix) {
    import std.conv: octal;
    auto script_path = temp_root.join("test-script.sh");
    script_path.writeFile(
        "#!" ~ nativeShell ~ newline ~
        `echo "Test out: $1 $2"` ~ newline);
    // Add permission to run this script
    script_path.setAttributes(octal!755);
} else version(Windows) {
    auto script_path = temp_root.join("test-script.cmd");
    script_path.writeFile(
        "@echo off" ~ newline ~
        "echo Test out: %1 %2" ~ newline);
}

// Test the case when process executes fine
auto result = Process(script_path)
    .withArgs("Hello", "World", "test")
    .execute
    .ensureOk;
result.status.should == 0;
result.output.chomp.should == "Test out: Hello World";
result.isOk.shouldBeTrue;
result.isNotOk.shouldBeFalse;
// When we expect different successful exit-code
result.isOk(42).shouldBeFalse;
result.isNotOk(42).shouldBeTrue;
result.ensureOk(42).shouldThrow!ProcessException;

Test simple execution of the script that handles environment variables

1 import std.string;
2 import std.ascii : newline;
3 
4 import unit_threaded.assertions;
5 
6 auto temp_root = createTempPath();
7 scope(exit) temp_root.remove();
8 
9 /* Do similar trick as in Phobos for portable newline output
10  *
11  * To avoid printing the newline characters, we use the echo|set trick on
12  * Windows, and printf on POSIX (neither echo -n nor echo \c are portable).
13  */
14 version(Posix) {
15     import std.conv: octal;
16     auto script_path = temp_root.join("test-script.sh");
17     script_path.writeFile(
18         "#!" ~ nativeShell ~ newline ~
19         `printf "Test out: $1 $2, $MY_PARAM_1 $MY_PARAM_2"` ~ newline);
20     // Add permission to run this script
21     script_path.setAttributes(octal!755);
22 } else version(Windows) {
23     auto script_path = temp_root.join("test-script.cmd");
24     script_path.writeFile(
25         `@echo off` ~ newline ~
26         `echo|set /p DUMMY="Test out: %1 %2, %MY_PARAM_1% %MY_PARAM_2%"` ~ newline);
27 }
28 
29 // Test the case when process executes fine
30 auto result = Process(script_path)
31     .withArgs("Hello")
32     .withArgs("World")
33     .withEnv("MY_PARAM_1", "the")
34     .withEnv("MY_PARAM_2", "Void")
35     .execute
36     .ensureOk;
37 result.status.should == 0;
38 result.output.chomp.should == "Test out: Hello World, the Void";
39 result.isOk.shouldBeTrue;
40 result.isNotOk.shouldBeFalse;
41 // When we expect different successful exit-code
42 result.isOk(42).shouldBeFalse;
43 result.isNotOk(42).shouldBeTrue;
44 
45 // Ensure that status is ok, if not ok, then raise error
46 result.ensureOk(42).shouldThrow!ProcessException;
47 
48 // Optionally allow to print command output on failure with custom error message or with standard one.
49 result.ensureOk("Custom error message", 42).shouldThrowWithMessage!ProcessException(
50     "Custom error message");
51 result.ensureOk("Error message", true, 42).shouldThrowWithMessage!ProcessException(
52     "Error message\nOutput: %s".format("Test out: Hello World, the Void"));
53 result.ensureOk(true, 42).shouldThrowWithMessage!ProcessException(
54     "Program %s with args %s failed! Expected exit-code %s, got %s.\nOutput: %s".format(
55         result._program, result._args, 42, 0, "Test out: Hello World, the Void"));

Test simple execution of the script with user (use current user)

import std.string;
import std.ascii : newline;

import unit_threaded.assertions;

auto temp_root = createTempPath();
scope(exit) temp_root.remove();

import std.conv: octal;
auto script_path = temp_root.join("test-script.sh");
script_path.writeFile(
    "#!" ~ nativeShell ~ newline ~
    `echo "Test out: $1 $2"` ~ newline);
// Add permission to run this script
script_path.setAttributes(octal!755);

auto username = Process("whoami").execute.ensureOk(true).output.strip;

// Test the case when process executes fine
auto result = Process(script_path)
    .withArgs("Hello", "World", "test")
    .withUser(username)
    .execute
    .ensureOk;
result.status.should == 0;
result.output.chomp.should == "Test out: Hello World";
result.isOk.shouldBeTrue;
result.isNotOk.shouldBeFalse;
// When we expect different successful exit-code
result.isOk(42).shouldBeFalse;
result.isNotOk(42).shouldBeTrue;
result.ensureOk(42).shouldThrow!ProcessException;

Test simple execution of the script within user's home directory

import std.string;
import std.ascii : newline;

import unit_threaded.assertions;

// Change current working dir to /tmp
Path.tempDir.chdir;

auto current_user = Process("whoami").execute.ensureOk(true).output.strip;
auto workdir = Process("pwd")
    .withUser(current_user)
    .execute
    .ensureOk(true)
    .output.strip;

Path(workdir).realPath.should == Path.tempDir.realPath;

workdir = Process("pwd")
    .withUser(current_user, true)
    .execute
    .ensureOk(true)
    .output.strip;

Path(workdir).realPath.should == Path("~").realPath;

Meta