Execute external program/command in PHP with stdin, stdout and stderr support

Sometimes we need to invoke an external command (program) from within a PHP script. For example, we might need to unzip files using the gunzip command. This can be done easily by invoking the exec() or system() PHP functions. But if our command reads the standard input and writes to the standard output?

The Problem

We need to execute an external command (program) that reads input from the standard input (stdin) and returns the result to the standard output (stdout). Optionally the command may return error information via standard error (stderr).

Discussion

We will use the HTML Tidy console application as a sample. The application reads html from the standard input (stdin), performs some tidy operations on it and outputs the clean HTML to the standard output (stdout).

Our aim will be to create a tidyHtml() function:

<?php ob_start(); ?>
<h1>Welcome</h1>
<a href="">Click to go...</a>
<?php
// The $buffer variable will hold the content to be passed as stdin when HTML Tidy command is executed.
$buffer = ob_get_clean();
 
// How the tidy command will be executed
$tidyCommandLine = 'c:/tidy/tidy.exe -ashtml';
print tidyHtml($buffer, $tidyCommandLine);
?>

Looking at PHP documentation, we see that there is a popen() function that makes a good candidate, but it supports data transfer in only one direction (only one pipe).

There is another function proc_open() that requires more work, but does exactly what we need.

The Solution

function tidyHtml( $input, $tidyCommandLine, $returnBodyOnly = true ) {
	$descriptorspec = array(
	   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
	   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
	   2 => array("pipe", "w"),  // stdout is a pipe that the child will write to
	);
 
	$process = proc_open($tidyCommandLine, $descriptorspec, $pipes);
	if ( ! is_resource($process)) return false;
 
	fwrite($pipes[0], $input);
	fclose($pipes[0]);
 
	$result = '';
	while ( ! feof($pipes[1]) ) {
		$result .= fread($pipes[1], 4096);
	}
 
	$errors = '';
	while ( ! feof($pipes[2]) ) {
		$errors .= fread($pipes[2], 4096);
	}
	fclose($pipes[1]);
 
	proc_close($process);
 
	if ( $returnBodyOnly ) {
		$result = preg_replace('!^.*<body>(.*)</body>.*$!s', '\\1', $result);
	}
 
	return $result;
}

The proposed solution does exactly what we need:

  1. It opens a new process with three communication channels (pipes) - one for stdin, one for stdout and one for stderr.
  2. Than it sends (pipes) the html content to the stdin of the HTML Tidy using the fwrite() function.
  3. Then the stdin is closed. This notifies the HTML Tidy to process the input.
  4. The HTML Tidy cleans the HTML and sends the clean result to the stdout.
  5. The function reads the stdout using the fread() function.
  6. The function also reads the stderr using the fread() function (Although we discard the error result, we show you how you can get access to it).
  7. The output of the HTML Tidy command is returned as function result.

Back to: PHP, Home

 
php/executecommandstdinout.txt · Last modified: 2009/10/31 23:36 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki