Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Child Processes

Tango has support for creating and communicating with external child processes through the tango.sys.Process module and the Process class is the one that handles the interaction with them.

Process Attributes

Each process has several attributes that you can modify for your purposes. The first of these attributes are the arguments received by the process, which also include the program name as the first argument.

Arguments

You normally set the arguments when calling the constructor of the Process class or via the args property. You have 4 ways to do it:

  1. Using multiple strings (variadic arguments) in a constructor:
    Process p = new Process("ping", "-n", "4", "localhost");
    
  2. Using a single string with the command line in a constructor:
    Process p = new Process("ping -n 4 localhost", null);
    
  3. Using an array in a constructor:
    char[][] args;
    
    args ~= "ping";
    args ~= "-n";
    args ~= "4";
    args ~= "localhost";
    
    Process p = new Process(args, null);
    
  4. Using the args property with an array:
    Process p = new Process();
    char[][] args;
    
    args ~= "ping";
    args ~= "-n";
    args ~= "4";
    args ~= "localhost";
    
    p.args = args;
    

If you look closely you'll see that in the invocations on both items 2 and 3 the constructor receive an extra parameter that we're setting to null. This argument is used to set the process' environment variables, which we'll see in the next section.

Arguments with Embedded Spaces

When using a single string to specify the process' command line you might need to use spaces within single arguments. In those cases you have to use use double-quotes (") as argument delimiters. For example:

Process p = new Process("xcopy \"C:\\My Folder\\*.zip\" C:\\temp /S", null);

Wildcard Expansion

The Process class does not do any wildcard expansion of its arguments. This may look strange to users of bash or any other Unix shell. If you want to expand wildcards, on Unix systems you can usually do so by calling your system's shell like this:

Process p = new Process("/bin/sh", "-c", "cp *.zip /tmp");

If you are using Windows there is no easy alternative as traditionally each program expands the wildcards present in its command line.

Environment Variables

Each process has a set of environment variables that it can read and write to. Normally we would use the tango.sys.Environment module to do this (or the getenv()/putenv() standard C APIs). In the Process class, if we pass null as the array of environment variables, what we are saying is that in the child process we want to inherit the variables from the parent process. When we don't want to inherit the environment variables, we can set them individually in the constructor or via the env property like this:

char[][char[]] env;

env["VARIABLE_1"] ~= "VALUE_1";
env["VARIABLE_2"] ~= "VALUE_2";
env["VARIABLE_3"] ~= "VALUE_3";
env["PATH"] ~= "/home/tango/bin";

// First alternative using a constructor
Process p = new Process("my_process arg1 arg2", env);
// Second alternative using the 'env' property
Process q = new Process();

q.env = env;

Working Directory

Sometimes we want our child process to run in a specific directory. When using the Process class we can use the workDir property before executing the process to achieve this:

p.workDir = "C:\\Documents and Settings\\tango\\scripts";

Executing a Process

Once you've set all the attributes correctly you are ready to run your process. You can do so by calling the execute() method. After this method returns your process is already running. execute() allows arguments similar to those of the constructors:

  1. Using multiple strings (variadic arguments):
    p.execute("ping", "-n", "4", "localhost");
    
  2. Using a single string with the command line:
    p.execute("ping -n 4 localhost", null);
    
  3. Using an array:
    char[][] args;
    
    args ~= "ping";
    args ~= "-n";
    args ~= "4";
    args ~= "localhost";
    
    p.execute(args, null);
    

Communicating with a Process

On the platforms where Tango runs, each process always has at least 3 open file descriptors or handles. These are stdin (standard input), stdout (standard output) and stderr (standard error output). Normally a console process will read data from stdin and write its output to stdout and to stderr (in case of errors). The Process class provides methods to access each of these file descriptors as IConduit's. The conduits have the expected names (i.e. stdin, stdout and stderr), but what might be confusing is that as you're connecting to these conduits from the parent process stdin is a write conduit and stdout/stderr are read conduits.

A simple way to read the output of process p, for example, is by using the LineIterator template class:

foreach (line; new LineIterator!(char)(p.stdout))
{
    Cout(line).newline;
}

Or a condensed equivalent:

Cout.conduit.copy(p.stdout);


Getting the Result of a Process

Whenever you run a child process you will normally want to know when it has finished and what was the reason why it finished (i.e. was it a normal exit?; did it crash?; etc.). The method you can use to get this information is wait(). This method returns a Process.Result struct, which has two members called reason and status that will indicate why the process finished. The reason member indicates what was the main cause why the process finished; status gives additional information related to the reason.

Here's a table that indicates what values you can expect in each of these members:

ReasonStatusDescription
ExitProcess return codeThe process exited normally
SignalSignal number used to kill the processThe process was killed by a signal
StopSignal number used to stop the processThe process was stopped
ContinueSignal number used to resume the processThe process was resumed
Errorerrno value of the failure if the process was running; -1 if notThe wait on the child process failed

In order to simplify error handling there is also a convenience method called Process.Result.toString() that will convert the process result into a human-readable message.

Killing a Process

You can easily kill a running process with the kill() method.

Avoiding Resource Leaks

It is important that every time you call execute() you also call wait() to free any resources that may have been allocated. This is particularly important on Windows, as the current implementation internally keeps an open handle to be able to monitor the child process.

Example

This is an example of a program that combines most of the features present in the tango.sys.Process module. It calls the ping program via the system shell and uses an environment variable in the command line to set the host name we want to ping.

module test_process;

private import tango.io.Stdout;
private import tango.sys.Environment;
private import tango.sys.Process;
private import tango.core.Exception;


/**
 * Example program for the tango.sys.Process class.
 */
void main(char[][] args)
{
    const char[] Var = "TEST_HOST_NAME";

    char[][] command;

    // Array of arguments used to call 'ping' via the shell
    version (Windows)
    {
        command ~= "cmd.exe";
        command ~= "/c";
        command ~= "ping -n 4 %" ~ Var ~ "%";
    }
    else
    {
        command ~= "/bin/sh";
        command ~= "-c";
        command ~= "ping -c 4 $" ~ Var;
    }

    try
    {
        char[][char[]] env;

        // Get the current environment and add the variable we're using in the command line
        env = Environment.get();
        env[Var] = (args.length > 1 ? args[1] : "localhost");

        Process p = new Process(command, env);

        Stdout.formatln("Executing {}", p.toString);
        p.execute();

        // Show the normal output
        Stdout.formatln("Output from process '{}' (pid {})\n----------", p.programName, p.pid);
        Stdout.stream.copy(p.stdout);
        Stdout("----------").newline;

        // Show the errors (if there were any)
        Stderr.formatln("\nErrors from process '{}' (pid {})\n----------", p.programName, p.pid);
        Stderr.stream.copy(p.stderr);
        Stderr("----------").newline;

        Process.Result result = p.wait();

        Stdout.formatln("Process '{}' (pid {}) finished: {}", p.programName, p.pid, result.toString);
    }
    catch (ProcessException e)
    {
        Stderr("Process execution failed: ") (e.toString).newline;
    }
    catch (IOException e)
    {
        Stderr("Input/output exception caught: ")(e.toString).newline;
    }
    catch (Exception e)
    {
        Stderr("Unexpected exception caught: ")(e.toString).newline;
    }
}