Skip to content

Instantly share code, notes, and snippets.

@swichers
Last active March 24, 2022 10:58
Show Gist options
  • Save swichers/027d5ae903350cbd4af8 to your computer and use it in GitHub Desktop.
Save swichers/027d5ae903350cbd4af8 to your computer and use it in GitHub Desktop.
Capturing STDERR from interactive proc_open call
{
"name": "swichers/passthru_with_errors",
"description": "An enhanced passthru() command that includes error capturing.",
"license": "Apache-2.0",
"authors": [ { "name": "Steven Wichers", "role": "Developer" } ],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"files": ["passthru_with_errors.php"]
}
}
<?php
/**
* @file
* Demonstrates how to execute an interactive process using proc_open while
* also grabbing STDERR for your own use.
*
* I did not like the other options that redirected output to a file on disk, or
* used some other more complex way to grab STDERR.
*
* I would like to eliminate the loop in favor of a stream capture of some sort,
* but came up short.
*/
// Prevent defining the function multiple times.
if (!function_exists('passthru_with_errors')) {
/**
* Executes an interactive command while capturing STDERR and the return code.
*
* @param string $command
* The command that will be executed.
*
* @param int &$return_var
* If the return_var argument is present, the return status of the Unix
* command will be placed here.
*
* @param array &$stderr_ouput
* If the stderr_ouput argument is present, the output sent to STDERR.
*
* @return
* No value is returned.
*/
function passthru_with_errors($command, &$return_var = null, array &$stderr_ouput = null) {
$return_var = null;
$stderr_ouput = array();
$descriptorspec = array(
// Must use php://stdin(out) in order to allow display of command output
// and the user to interact with the process.
0 => array('file', 'php://stdin', 'r'),
1 => array('file', 'php://stdout', 'w'),
2 => array('pipe', 'w'),
);
$pipes = array();
$process = @proc_open($command, $descriptorspec, $pipes);
if (is_resource($process)) {
// Loop on process until it exits normally.
do {
$status = proc_get_status($process);
// If our stderr pipe has data, grab it for use later.
if (!feof($pipes[2])) {
// We're acting like passthru would and displaying errors as they come in.
$error_line = fgets($pipes[2]);
echo $error_line;
$stderr_ouput[] = $error_line;
}
} while ($status['running']);
// According to documentation, the exit code is only valid the first call
// after a process is finished. We can't rely on the return value of
// proc_close because proc_get_status will read the exit code first.
$return_var = $status['exitcode'];
proc_close($process);
}
}
}
@paco3346
Copy link

This is genius! I've been fighting with proc_open() for a while and just hit a mental block on how to allow the process to keep running since get_stream_contents() blocks until there's content. Didn't even think about fgets(). Good work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment