Article

Home » Server-side Coding » PHP & MySQL Tutorials » PHP on the Command Line - Part 2

About the Author

Harry Fuecks

author_HarryF Harry has been working in corporate IT since 1994, with everything from start-ups to Fortune 100 companies. Outside of office hours he runs phpPatterns: a site dedicated to software design with PHP that aims to raise standards of PHP development. He also maintains Dynamically Typed: SitePoint's PHP blog.

View all articles by Harry Fuecks...

PHP on the Command Line - Part 2

By Harry Fuecks

August 11th, 2004

Reader Rating: 9.5

Page: 1 2 3 Next

In Part 1 of this tutorial, we had a look at PHP's command line SAPI (server API). In this article, we'll take things a step further, and see how you can hook up a command line PHP script with existing command line tools provided by your operating system.

In contrast to the last article, the focus of this article will be Unix-based systems -- something which can't be avoided. To steal a quote from Linux.com's CLI for Noobies series:

"[Comparing] the command line environment of DOS/Windows with that of Linux, [is] like comparing a wheelbarrow with an 18-wheeler."

I'll assume that you're using PHP 4.3.x+ with the CLI SAPI to execute PHP scripts on the command line. The examples provided expect to find the CLI binary at /usr/local/bin/php. If your setup is different than this, modifying the first line should remedy the situation in each case. Note that you can download the complete code archive for this tutorial here.

Today's Command Line options:

  • Shell Execution: run external programs from PHP
  • Security Issues: terrorists threaten the command line!
  • Working with the Environment: talk to Mother Operating System
  • Process Control: it kills me

Shell Execution

One of the advantages of working on Unix-based systems is the wealth of command line tools that are available to solve most any common problem you can think of.

This becomes particularly important when you're writing PHP scripts for the command line. Performing a search and replace over a group of text files, for example, is best left to tools like grep and sed, both for performance and to save precious development hours. That's not to say that you shouldn't use PHP as a "front end" to perform search and replace operations, but that you should delegate the "real work" to existing tools whenever possible.

PHP provides a number of functions for executing external programs via the shell, namely shell_exec(), passthru(), exec(), popen(), all of which do essentially the same thing, but provide different "interfaces" to the external program. Each of these functions can only capture output written to the STDOUT pipe; each will spawn a child process within which the external program will be executed.

Two further functions exist to execute external programs: proc_open() and pcntl_exec(). Their use and behaviour differs from the preceding functions, so we'll examine them separately.

What's my Extension?

To give us a "known quantity" that we can execute with other PHP scripts, first up, we'll look at a script that examines the PHP extensions available in any given installation. I'll let the code explain itself; everything you'll see here, you saw in the last article:

#!/usr/local/bin/php
<?php
# Include PEAR::Console_Getopt
require_once 'Console/Getopt.php';

// Define error return codes
define('INVALID_PHP_SAPI',3);
define('UNKNOWN_EXTENSION',4);

//--------------------------------------------------------------------------------------
/**
* Displays the usage of this script
* Called when -h option specified or on illegal option
*/
function usage() {
 $usage = <<<EOD
Usage: ./extensions.php [OPTION]
Lists the loaded PHP extensions or shows the functions
a single extension makes available
 -e=EXTENSION  Name of extension to list functions for
 -h    Display usage

EOD;
 fwrite(STDOUT,$usage);
 exit(0);
}

//--------------------------------------------------------------------------------------
/**
* Gets the name of an extension from -e option
* (defaults to NULL)
*/
function getExtension() {

 $args = Console_Getopt::readPHPArgv();

 // Could be an error with older PHP versions and the CGI SAPI
 if ( PEAR::isError($args) ) {
   fwrite(STDERR,$args->getMessage()."\n");
   exit(INVALID_PHP_SAPI);
 }

 // Compatibility between "php extensions.php" and "./extensions.php"
 if ( realpath($_SERVER['argv'][0]) == __FILE__ ) {
   $options = Console_Getopt::getOpt($args,'he:');
 } else {
   $options = Console_Getopt::getOpt2($args,'he:');
 }

 // Check for invalid options
 if ( PEAR::isError($options) ) {
   fwrite(STDERR,$options->getMessage()."\n");
   usage();
 }

 // Set default length
 $extension = NULL;

 // Loop through the user provided options
 foreach ( $options[0] as $option ) {
   switch ( $option[0] ) {
     case 'h':
       usage();
     break;
     case 'e':
       $extension = $option[1];
     break;
   }
 }

 return $extension;
}

//--------------------------------------------------------------------------------------
// Get a list of extensions
$extensions = get_loaded_extensions();

$extension = getExtension();

// If it's not null, the -e option was used
if ( !is_null($extension) ) {

 // Does the extension actually exist?
 if ( in_array($extension,$extensions) ) {

   // Display the list of functionsthe extension provides
   $funcs = get_extension_funcs($extension);
   foreach ( $funcs as $func ) {
     fwrite(STDOUT,$func."\n");
   }

 } else {

   fwrite(STDERR,"Unknown extension $extension\n");
   exit(UNKNOWN_EXTENSION);

 }
 
} else {

 // Display the list of extension
 foreach ( $extensions as $extension ) {
   fwrite(STDOUT, $extension."\n");
 }
 
}

exit(0);
?>

Filename: extensions.php

The code is executed using this command:

$ ./extensions.php

When executed, the above script displays a list of loaded PHP extensions by default. However, if the command line '-e' option is provided, along with an extension name, it lists the functions the extension provides.

$ ./extensions.php -e mysql

The above command displays a list of the functions provided by the MySQL extension (or whichever extension name you provided).

Don't forget that you need to make the script executable by setting the correction permissions as follows:

$ chmod +x extensions.php

Now that I've got a simple program to work with, I can execute it from another script.

shell_exec()

The shell_exec() command is actually an alias for the backtick operator. It allows you to execute an external program via the shell and have the results returned to you as a string.

Whether you use the backtick operator or the shell_exec() command is up to you.

Personally, I prefer the latter as it clearly states what your code is doing and is easy to see in the source code.

Here's a basic example:

#!/usr/local/bin/php
<?php
$result = shell_exec('./extensions.php');
fwrite(STDOUT,$result);
exit(0);
?>

Filename: shell_exec1.php

As you can see, using shell_exec(), I can simply name an external program that I want to execute, and get the results back in the returned variable. There's one important requirement for the above example to work -- the shell_exec1.php script must execute from the directory in which extensions.php resides. We'll look at this point further when we consider the environment below.

We can also pass command line options via shell_exec(). Let's look at an example:

#!/usr/local/bin/php
<?php
$result = shell_exec('./extensions.php -e mysql');
fwrite(STDOUT,$result);
exit(0);
?>

Filename: shell_exec2.php

In other words, the command we tell shell_exec() to execute can be anything we can type ourselves, from the command line. For example, I can pipe the output of extensions.php through grep, filtering for all extensions that begin with the letter 'p':

#!/usr/local/bin/php
<?php
$result = shell_exec('./extensions.php | grep -w "^p.*"');
fwrite(STDOUT,$result);
exit(0);
?>

Filename: shell_exec3.php

This returns the following list (from my system):

posix
pgsql
pcre
pcntl

One important thing to note about shell_exec() is that it only returns output from STDOUT. Take a look at this example:

#!/usr/local/bin/php
<?php
$result = shell_exec('./extensions.php -e hair 2>/dev/null');

// Assumes that nothing was written to STDOUT if there was an error
if ( !empty($result) ) {
 fwrite(STDOUT,$result);
} else {
 fwrite(STDERR,"An error occurred\n");
}
exit(0);
?>

Filename: shell_exec4.php

Here, I've redirected the STDERR stream to Unix's NULL device (I'm throwing it away). If I execute my extensions.php script with an extension name that doesn't exist, nothing is written to STDOUT, so the above script can display a (somewhat useless) message that an error occurred.

Note that if I don't redirect STDERR to the null device, I will still see the error message ("Unknown extension hair") from my shell, the error having "bypassed" shell_exec4.php.

passthru()

The passthru() function allows you to execute an external program and display its results directly:

#!/usr/local/bin/php
<?php
passthru('./extensions.php');
exit(0);
?>

Filename: passthru1.php

That's all you need to do (passthru() doesn't return anything).

It does take a second argument, though -- a variable that's populated with the return code of the external program. For example:

#!/usr/local/bin/php
<?php
$return_code = 0;

passthru('./extensions.php -e hair 2>/dev/null',$return_code);

if ( $return_code != 0 ) {
 fwrite(STDERR,"There was an error!\n");
}
exit(0);
?>

Filename: passthru2.php

This provides me with a more accurate mechanism to check for errors than did shell_exec(), with which I was forced to assume that nothing would be written to STDOUT if an error occurred.

exec()

The exec() function provides another variation on the theme. It returns the last line of output from the external program, but also (optionally) populates an array with the full output and makes the return code available. If I assume that most programs will return a single-line error message, if indeed there was one, exec() can be pretty handy. Let's see another example:

#!/usr/local/bin/php
<?php
$return_code = 0;
$result = array();

$error = exec('./extensions.php -e hair 2>&1',$result, $return_code);

if ( $return_code != 0 ) {
 fwrite(STDERR,"Error: $error\n");
} else {
 $result = implode("\n",$result);
 fwrite(STDOUT,$result);
}
exit(0);
?>

Filename: exec1.php

First, I redirect the STDERR stream to STDOUT, so that I can capture both from my script. I use the $error variable only if $return_code is non-zero. Otherwise, I can display the contents of the $result array.

system()

The system() function provides yet another variation on the theme -- something between passthru() and exec(). Like passthru(), it also outputs directly anything it receives from the external program, on the STDOUT stream. However, like exec(), it also returns the last line output, and makes the return code available. For example:

#!/usr/local/bin/php
<?php
$return_code = 0;
$result = array();

$error = system('./extensions.php -e hair 2>&1',$return_code);

if ( $return_code != 0 ) {
 // Fictional logger
 # Logger::log($error);
}
exit(0);
?>

Filename: system1.php

An important difference in this code is that, though the error message will still be displayed immediately to the shell, I also have the opportunity to log the error message.

popen()

The popen() function allows me to work with an external program as if I were working with a file. For example:

#!/usr/local/bin/php
<?php
$fp = popen('./extensions.php','r');

while ( !feof($fp) ) {
 fwrite(STDOUT, fgets($fp));
}

pclose($fp);
exit(0);
?>

Filename: popen1.php

As with all the preceding functions, popen() only reads from STDOUT. As such, the following example will display nothing:

#!/usr/local/bin/php
<?php
$fp = popen('./extensions.php -e hair 2>/dev/null','r');

while ( !feof($fp) ) {
 fwrite(STDOUT, fgets($fp));
}

pclose($fp);
exit(0);
?>

Filename: popen2.php

One thing that's special about popen() is it also allows you to write to the program's STDIN stream, in the way we saw in the last tutorial.

Let's imagine I have a simple script like this:

#!/usr/local/bin/php
<?php
while ( trim($line = fgets(STDIN)) != 'exit' ) {

 fwrite(STDOUT,$line);

}

exit(0);
?>

Filename: reader.php

If I execute this script myself, it simply displays everything I type in, line by line, until I enter the string 'exit'.

I can now write to this program with popen() like so:

#!/usr/local/bin/php
<?php
$colors = array('red','green','blue');

// Open program in write mode
$fp = popen('./reader.php','w');

foreach ( $colors as $color ) {
 fwrite($fp,$color."\n");
}

fwrite($fp,"exit\n");

pclose($fp);
exit(0);
?>

Filename: popen3.php

If I run this script, the three colors in the $colors array will be displayed on new lines in my terminal. Because I opened the program with popen() in write mode, instead of capturing the STDOUT stream, it uses STDIN instead, which llows the results to remain visible.

Note the popen() is uni-directional. You can only read or write to an external program, not both (although the PHP manual has some interesting submitted hacks to get round this).

With PHP 4.3.x+ the function proc_open() allows you to work with more than one IO pipe at once, as we'll see later.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links