PHP on the Command Line – Part 1 Article

Tweet
Parsing Options

While, at first glance, it may not seem too hard to write a function for parsing options, why would you bother? PEAR::Console_Getopt provides a ready-rolled solution, and solves the issues with $argv vs. $_SERVER['argv'] you saw earlier.

Note the version of Console_Getopt I used here was 1.2. See Getting Started with PEAR if you’re wondering how to install it. Be warned that uninstalling Console_Getopt is a bad idea, as the package manager itself depends on it. If you need to upgrade your version, use:

$ pear upgrade Console_Getopt-stable

PEAR::Console_Getopt provides three public methods, each of which is called statically:

1. array Console_Getopt::readPHPArgv()

The array returned from readPHPArgv() is the same as the $argv array, but Console_Getopt offers better protection against different PHP versions. If it is unable to fetch access the command line arguments (e.g. if they’re not available, such as with a CGI binary where register_argc_argv=Off), it returns a PEAR Error object.

2. array Console_Getopt::getOpt(array argv, [string short_opts], [array long_opts])

The getOpt() method takes the array of command line arguments and parses them into an array of options and arguments. It expects the first element of the argv to be the name of the script that’s being executed, and it’s meant for use when the following types of scripts are executed:

$ php some_script.php

The ‘short_opts‘ string identifies short options that the user can provide, followed by characters that identify given options. It allows two special markers in the string:
:‘ specifies that the preceding letter must be followed by a “value”, while ‘::‘ says that the preceding option may be followed by a value. It’s easiest to see this by example.

If $short_opts = 'lt', the following command lines are possible (no errors will be returned from getOpt()):

$ php some_script.php -lt
$ php some_script.php -tl
$ php some_script.php -l -t
$ php some_script.php -t -l
$ php some_script.php -l
$ php some_script.php -t -t -l -l
$ php some_script.php -t
$ php some_script.php

Setting $short_opts = 'lt:' means the ‘t’ option must be followed by at least one character (not a space), which will be its value. Look at this example:

$ php some_script.php -ltv

Here 't' = 'v'.

$ php some_script.php -tvalue -l

Now 't' = 'value'.

$ php some_script.php -tl

Here 't' = 'l' (l is not an option in this case).

Finally, setting $short_opts = 'lt::' means that ‘t’ can have an optional value (anything that proceeds it, up to the next space). Note that I can place further options after the colons in the short options string. For example, 'lt:o::B'.

This may seem a little arcane, but specifying the short options string in this way is fairly standard in many languages used on UNIX, from C to Python. Once you get used to it, this method provides a useful mechanism to get the most out of command line arguments with minimum effort.

The long_opts are specified in a similar manner to the short_opts, but, instead of using a string, an array is used; also, the ‘:‘ marker is replaced with an = sign to identify when values should be given. To allow the long options ‘--user=harryf --pass=secret‘, I’d need an array like this:

$long_opts = array (
'user=',
'pass=',
);

In this case, both the 'user' and 'pass' options require a value. If I omit the equals sign, no value is allowed, while using ‘==‘ allows the user to provide an optional value.

The array returned from getOpt() always has two elements in its first order; the first contains the command line options, while the second contains the arguments. We’ll look at the structure in more detail in a moment.

The final method provided by Console_Getopt is:

3. array Console_Getopt::getOpt2(array argv, [string short_opts], [array long_opts])

This method is almost exactly the same as getOpt(), except that it expects the first element in the argv array to be a real argument, not the name of the script that’s being executed. In other words, you’d use it when executing a script such as:

$ some_script.php -tl

Here’s a simple example of PEAR::Console_Getopt in action:

<?php
// Include PEAR::Console_Getopt
require_once 'Console/Getopt.php';

// Define exit codes for errors
define('NO_ARGS',10);
define('INVALID_OPTION',11);

// Reading the incoming arguments - same as $argv
$args = Console_Getopt::readPHPArgv();

// Make sure we got them (for non CLI binaries)
if (PEAR::isError($args)) {
fwrite(STDERR,$args->getMessage()."n");
exit(NO_ARGS);
}

// Short options
$short_opts = 'lt:';

// Long options
$long_opts = array(
'user=',
'pass=',
);

// Convert the arguments to options - check for the first argument
if ( realpath($_SERVER['argv'][0]) == __FILE__ ) {
$options = Console_Getopt::getOpt($args,$short_opts,$long_opts);
} else {
$options = Console_Getopt::getOpt2($args,$short_opts,$long_opts);
}

// Check the options are valid
if (PEAR::isError($options)) {
fwrite(STDERR,$options->getMessage()."n");
exit(INVALID_OPTION);
}

print_r($options);
?>

If we invoke this script like so:

$ php getopts.php -ltr --user=harryf --pass=secret arg1 arg2

The output from print_r() looks like this:

Array
(
[0] => Array
(
[0] => Array
(
[0] => l
[1] =>
)

[1] => Array
(
[0] => t
[1] => r
)

[2] => Array
(
[0] => --user
[1] => harryf
)

[3] => Array
(
[0] => --pass
[1] => secret
)

)

[1] => Array
(
[0] => arg1
[1] => arg2
)

)

The options array always contains two first order elements, the first of which contains another array that represents each command line option. The second element contains an array of arguments. If you look back at how the script was invoked, you should be able to see the relationship.

You can see how PEAR::Console_Getopt might be used to parse the arguments and options used with the UNIX ‘ls’ utility when executed as follows:

$ ls -ltr --width=80 /home/harryf/scripts/

If you parsed the above command line with Console_Getopt, you would have an array of options such as:

Array
(
[0] => Array
(
[0] => Array
(
[0] => l
[1] =>
)

[1] => Array
(
[0] => t
[1] =>
)

[2] => Array
(
[0] => r
[1] =>
)

[3] => Array
(
[0] => --width
[1] => 80
)
)

[1] => Array
(
[0] => /home/harryf/scripts/
)

)

You’re now set to re-implement ‘ls’ in PHP, should you have endless hours on your hands.

Compatibility

As I mentioned earlier in this article, it’s possible to use the PHP CGI binary in more or less the same way as the CLI binary, but some additional “tweaking” is needed. Some of these changes are best made at runtime by including an additional PHP script. Others need to be made either in php.ini itself, or by passing additional arguments to the PHP binary.

Starting with the settings that you can change at runtime, here’s a script that fixes most of the issues for users working with the CGI binary, or CLI versions below 4.3.0:

<?php
/**
* Sets up CLI environment based on SAPI and PHP version
*/
if (version_compare(phpversion(), '4.3.0', '<') || php_sapi_name() == 'cgi') {
// Handle output buffering
@ob_end_flush();
ob_implicit_flush(TRUE);

// PHP ini settings
set_time_limit(0);
ini_set('track_errors', TRUE);
ini_set('html_errors', FALSE);
ini_set('magic_quotes_runtime', FALSE);

// Define stream constants
define('STDIN', fopen('php://stdin', 'r'));
define('STDOUT', fopen('php://stdout', 'w'));
define('STDERR', fopen('php://stderr', 'w'));

// Close the streams on script termination
register_shutdown_function(
create_function('',
'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;')
);
}
?>

Filename: cli_compatibility.php

Including this code in your scripts will resolve most of the problems, but some further issues remain.

The CGI executable normally sends HTTP headers when executed — even from the command line. Users are likely to see output similar to the following when they execute command line scripts using the CGI executable:

X-Powered-By: PHP/4.3.6
Content-type: text/html

To prevent this, PHP needs to be invoked with the ‘-q’ option for ‘quiet’. For example:

$ php -q some_script.php

Unfortunately, this places the burden on users. On UNIX, you can get round this issue by adding the option to the SheBang as a script, and encouraging users to execute it directly:

#!/usr/local/bin/php -q
<?php
// Code starts here

Another issue with the CGI executable is that it changes automatically the current working directory to that in which the script that’s being executed resides. Imagine I use a simple script that contains the following:

<?php
print getcwd()."n";
?>

Filename: getcwd.php

Now, I execute it like so:

$ pwd
/home/harryf
$ php ./scripts/getcwd.php

If I’m using the CLI binary, this will display “/home/harryf” — my current directory. But, if I use the CGI binary, I get “/home/harryf/scripts” — the scripts directory.

I’m not aware of a workaround that would solve this issue, so, if it’s critical for your application, your best bet is to force users to work with the CLI binary. Change the start of the compatibility script above to:

<?php
if ( php_sapi_name() == 'cgi' ) {
die ('Unsupported SAPI - please use the CLI binary');
}
if ( version_compare(phpversion(), '4.3.0', '<') ) {

Wrap Up

That finishes this first part of our tour of PHP’s command line interface. So far, we’ve got the basics covered. Next time, I’ll discuss the execution of external programs from PHP, we’ll explore some of the packages PEAR has to offer for sprucing up your output, and we’ll take a look at some of the (UNIX only) extensions PHP provides for the command line.

Go to page: 1 | 2 | 3

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments