How to wait for a process executed by proc_open() in php?

后端 未结 2 1654
悲哀的现实
悲哀的现实 2021-01-03 10:17

please after reading this question , do not say that it is copied.I have already searched on web but none of the solution worked for me.

Wha

相关标签:
2条回答
  • 2021-01-03 11:01

    Here's my go at it (reading both stdout and stderr (hopefully) without deadlocks):

    class ExecResult {
        public $returnValue;
        public $stdoutBuffer;
        public $stderrBuffer;
    }
    
    class WtfPhpWhyIHaveToDoEverythingMyself {
        public static function exec($proc, $argv) {
            $cwd = getcwd();
            $env = [];
    
            $procAndArgv = count($argv) > 0 ?
                $proc . ' ' . implode(' ', self::escapeArgvs($argv)) :
                $proc;
    
            $pipes = null; // will get filled by proc_open()
    
            $result = new ExecResult();
    
            $processHandle = proc_open(
                $procAndArgv,
                [
                    0 => ['pipe', 'r'], // read/write is from child process's perspective
                    1 => ['pipe', 'w'],
                    2 => ['pipe', 'w']
                ],
                $pipes,
                $cwd,
                $env);
    
            $stdin = $pipes[0];
            $stdout = $pipes[1];
            $stderr = $pipes[2];
    
            fclose($stdin);
    
            stream_set_blocking($stdout, false);
            stream_set_blocking($stderr, false);
    
            $outEof = false;
            $errEof = false;
    
            do {
                $read = [ $stdout, $stderr ]; // [1]
                $write = null; // [1]
                $except = null; // [1]
    
                // [1] need to be as variables because only vars can be passed by reference
    
                stream_select(
                    $read,
                    $write,
                    $except,
                    1, // seconds
                    0); // microseconds
    
                $outEof = $outEof || feof($stdout);
                $errEof = $errEof || feof($stderr);
    
                if (!$outEof) {
                    $result->stdoutBuffer .= fgets($stdout);
                }
    
                if (!$errEof) {
                    $result->stderrBuffer .= fgets($stderr);
                }
            } while(!$outEof || !$errEof);
    
            fclose($stdout);
            fclose($stderr);
    
            $result->returnValue = proc_close($processHandle);
    
            return $result;
        }
    
        private static function escapeArgvs($argv) {
            return array_map(function ($item){
                return escapeshellarg($item);
            }, $argv);
        }
    }
    
    $result = WtfPhpWhyIHaveToDoEverythingMyself::exec('/var/www/html/hello.sh', [
        'arg 1',
        'arg 2',
    ]);
    
    var_dump($result);
    
    /*
    object(ExecResult)#1 (3) {
      ["returnValue"]=> int(0)
      ["stdoutBuffer"]=>
      string(45) "var1 = arg 1
    var2 = arg 2
    "
      ["stderrBuffer"]=>
      string(0) ""
    }
    */
    

    I am really disappointed that with PHP, you have to write all the plumbing yourself. For example node.js has got your back covered with simple child_process.spawn()

    0 讨论(0)
  • 2021-01-03 11:03

    It's best to use pipes for this problem. pcntl_wait() is Linux only, and it isn't very useful (in my experience) unless your dealing with fork(). Use stream_select for waiting. I'll look further into my old scripts if needed, but it's something like:

    <?php
    
    $cwd = "/code/";
    
    chmod("/code/test.c",0777);
    
    switch($xtension)
    {
        case "c":
            $cmd = "gcc -g test.c -o test -lm ";
        break;
        case "cpp":
            $cmd = "g++ -g test.cpp -o test";
        break;
        case "java":
            $cmd = "javac test.java";
        break;
    }
    
    $descriptors = array(   0 => array('pipe', 'r'), // stdin
                            1 => array('pipe', 'w'), // stdout
                            2 => array('pipe', 'a')  // stderr
    );
    
    $process = proc_open($cmd,$descriptors,$pipes,$code);
    
    stream_set_blocking($pipes[2], 0);
    
    if(($error = stream_get_contents($pipes[2]) !== false)
    {
        // error starting command (error *in* command)
        die($error);
    
    }else
    {
        stream_set_blocking($pipes[2], 1);
    
        /* Prepare the read array */
        $read   = array($pipes[1], $pipes[2]);
        $write  = NULL;
        $except = NULL;
        $tv_sec = 0;      // secs
        $tv_usec = 1000;  // millionths of secs
    
        if(false === ($rv = stream_select($read, $write, $except, $tv_sec, $tv_usec)))
        {
            die('error in stream_select');
    
        }else if ($rv > 0)
        {
            foreach($read as $pipe_resource)
            {
                if($pipe_resource == $pipes[1])
                {
                    // is stdout
    
                }else if($pipe_resource == $pipes[2])
                {
                    // is stderr
                }
            }
    
        }else
        {
            // select timed out. plenty of stuff can be done here
        }
    }
    
        // this will be executed after the process terminates
    

    There are many ways that you can use this to solve your problem.

    First, you can set the timeout in select to zero and it will wait indefinitely. If your executable doesn't use stdout, the stream_select function will block until the pipes are closed when the process terminates.

    If your process does use stdout and stderr you can use the select in a loop and check if the pipes are closed each time select returns. This will also allow you to usefully process information from stdout and stderr if needed in the future (json is awesome for this).

    0 讨论(0)
提交回复
热议问题