/**
* Execute an external program, display raw output in real-time, and buffer output pipes (stdout, stderr) for further processing.
*
* @param string|array $cmd Command to execute (not escaped)
* @param string[] $output_buffers Buffers to capture
* @return int Exit status code
*/
function tee($cmd, &...$output_buffers) {
$proc = proc_open($cmd, array_fill(1, count($output_buffers), ['pipe', 'w']), $pipes);
if($pipes) {
$filePointers = [];
foreach($pipes as $p => $pipe) {
stream_set_blocking($pipe, 0);
$filePointers[$p] = fopen("php://fd/$p", 'w');
}
foreach(array_keys($output_buffers) as $b) {
$output_buffers[$b] = '';
}
$sleep = 10*1000;
while($output_buffers) {
$readSuccess = false;
foreach(array_keys($output_buffers) as $b) {
$p = $b+1;
if(feof($pipes[$p])) {
fclose($pipes[$p]);
unset($pipes[$p]);
unset($output_buffers[$b]);
} else {
$line = fgets($pipes[$b + 1]);
if(strlen($line)) {
$readSuccess = true;
fwrite($filePointers[$p], $line);
$output_buffers[$b] .= $line;
}
}
}
if($readSuccess) {
$sleep = 10*1000;
} else {
usleep($sleep);
if($sleep < 150*1000) {
$sleep *= 2;
}
}
}
foreach($filePointers as $fp) {
fclose($fp);
}
}
return proc_close($proc);
}
I wrote this function because I write a lot of PHP-CLI scripts that interface with other shell programs. Often passthru
works well for this, but sometimes you also want to capture STDERR
in the event that the status code doesn't give enough information. This function lets you do that.
This commenter says that we have to put the pipes into non-blocking mode or the process can hang while it's waiting for you to read the other pipes, so that's what I've done.
Example usage:
tee("echo 'hello'; (>&2 echo \"error\"); echo 'world'", $stdout, $stderr);
dump($stdout);
dump($stderr);
Output:
hello
error
world
"""
hello\n
world\n
"""
"error\n"
sleep
so I don't know why it wouldn't display the question. \$\endgroup\$