DanielM
DanielM

Reputation: 6666

Capture/supress all output from php exec including stderr

I want to run several commands via exec() but I don't want any output to the screen. I do, however, want to hold on to the output so that I can control verbosity as my script runs.

Here is my class:

<?php
class System
{
    public function exec($command, array &$output = [])
    {
        $returnVar = null;
        exec($command, $output, $returnVar);
        return $returnVar;
    }
}

The problem is, most applications put an irritating amount of irrelevant stuff into stderr, which I don't seem to be able to block. For example, here's the output from running git clone through it:

Cloning into '/tmp/directory'...
remote: Counting objects: 649, done.
remote: Compressing objects: 100% (119/119), done.
remote: Total 649 (delta 64), reused 0 (delta 0), pack-reused 506
Receiving objects: 100% (649/649), 136.33 KiB | 0 bytes/s, done.
Resolving deltas: 100% (288/288), done.
Checking connectivity... done.

I've seen other questions claim that using the output buffer can work, however it doesn't seem to work

<?php
class System
{
    public function exec($command, array &$output = [])
    {
        $returnVar = null;
        ob_start();
        exec($command, $output, $returnVar);
        ob_end_clean();
        return $returnVar;
    }
}

This still produces the same results. I can fix the problem by routing stderr to stdout in the command, however, not only does this prevent me from differentiating from stdout and stderr, this application is designed to run in Windows and Linux, so this is now an unholy mess.

<?php
class System
{
    public function exec($command, array &$output = [])
    {
        $returnVar = null;

        // Is Windows
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            exec($command, $output, $returnVar);
            return $returnVar;
        }

        // Is not windows
        exec("({$command}) 2>&1", $output, $returnVar);
        return $returnVar;
    }
}

Is there a way to capture and suppress both stderr and stdout separately?

Update / Answer example

On the advice of @hexasoft in the comments, I updated my method to look like this:

<?php
class System
{
    public function exec($command, &$stdOutput = '', &$stdError = '')
    {
        $process = proc_open(
            $command,
            [
                0 => ['pipe', 'r'],
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ],
            $pipes
        );

        if (!is_resource($process)) {
            throw new \RuntimeException('Could not create a valid process');
        }

        // This will prevent to program from continuing until the processes is complete
        // Note: exitcode is created on the final loop here
        $status = proc_get_status($process);
        while($status['running']) {
            $status = proc_get_status($process);
        }

        $stdOutput = stream_get_contents($pipes[1]);
        $stdError  = stream_get_contents($pipes[2]);

        proc_close($process);

        return $status['exitcode'];
    }
}

This technique opens up much more advanced options, including asynchronous processes.

Upvotes: 5

Views: 1989

Answers (1)

hexasoft
hexasoft

Reputation: 677

Command proc_exec() allows to deal with file descriptors of the exec-ed command, using pipes.

The function is: proc_open ( string $cmd , array $descriptorspec , array &$pipes […optional parameters] ) : resource

You give you command in $cmd (as in exec) and you give an array describing the filedescriptors to "install" for the command. This array is indexed by filedescriptor number (0=stdin, 1=stdout…) and contains the type (file, pipe) and the mode (r/w…) plus the filename for file type.

You then get in $pipes an array of filedescriptors, which can be used to read or write (depending of what requested).

You should not forget to close these descriptors after usage.

Please refer to PHP manual page (and in particular the examples): https://php.net/manual/en/function.proc-open.php

Note that read/write is related to the spawned command, not the PHP script.

Upvotes: 3

Related Questions