SitePoint Sponsor
Article
SitePoint's PHP Column
PHP on the Command Line - Part 1
We all know PHP is a great language for building Web applications with but we all also know there's a lot more to running a serious Website than just the user interface.
There are back ups to be performed, log files to be analysed, data to be pruned and all those essential but mundane tasks that happen in the background. Typically, such routine administration tasks are prime targets for automation and, if you've spent any time with UNIX-based systems, you've probably run into the notion of shell scripts and cron.
What may or may not surprise you is that PHP does a pretty good job off the Web as well. If, after reading Getting Started with PEAR [1], you installed the PEAR package manager, you've already had a taste of what PHP can do on the command line.
In this two-part series, I'll be looking at PHP's Command Line Interface (CLI) in detail. This first part will concentrate on the fundamentals; the input and output to a command line script. The second will look further at what PEAR has to offer for the command line, as well as some of PHP's less charted functionality.
Today's Command Line Arguments
- Introducing the CLI: what it's all about
- Setting Up: checking your CLI
- Hello World: your first CLI script
- Stream In, Stream Out: dealing with input and output
- Multiple Choice: interactive user input
- Errors and Pipes: good manners
- Coping with Arguments: more input
- PEAR::Console_Getopt: managing arguments
- Compatibility: keeping everyone happy
Also, you might like to download the code archive [2] that contains all the examples we'll see in this tutorial.
Introducing the CLI
PHP's Command Line Interface first showed up in PHP 4.2.0, as experimental functionality. With PHP 4.3.0 the CLI became an official SAPI (Server API). What that means in practical terms is that the CLI version of PHP now comes as a separate PHP binary (executable), which you can use to run scripts on the command line, as well as providing "access points" to the rest of the world, much like the $_GET and $_POST variables that give you access to incoming data over the Web.
Before I go any further I should mention that, as you're probably aware, PHP is not the only choice for writing command line scripts. Both Perl and Python, to name just two, are widely used for writing command line applications and, in many cases, make better a choice than PHP. They provide a mature set of tools for common problems and, typically, better performance.
So, why use PHP? An obvious -- and very good -- reason may simply be that you know PHP better than the alternatives. Less obvious is that, if you're developing a Web application in PHP, writing supporting scripts in another language can lead to extra headaches -- even if you're confident in both. There's both the human aspect of having to switch programming "mind sets", and the overhead of having to support two platforms and the potential missed opportunities for re-use; data access logic may need to be implemented twice, for example.
What's more, if you're building a PHP application that will be used by "unknown" third parties, requiring them to set up other platforms in addition to PHP is likely to cause frustration, particularly if they're using Windows. Although PHP may not be the best choice overall for writing command line utilities, it's not a bad choice, either, and, in building tools to support an application that's already written in PHP, it makes sense to stick to a single platform.
What types of problems are suited to being solved with a command line script? Obvious choices are anything that relates to the administration of the server on which your Web application runs, such as backups, purging and archiving old data, and analysing log files -- perhaps sending an email alert should critical errors occur. These are all tasks that are suited to automation with cron [3], the "intended user" of the scripts being another script or application.
It needn't necessarily stop there, though. You might consider writing a command line "installer" for your Web application, to take care of copying PHP scripts to the right locations, setting up the correct file system permissions, loading the database schema and, for the intrepid, managing upgrades to the application. When executing PHP scripts over the Web via Apache, you typically don't have rights to complete tasks such as changing file system permissions – plus, you don't necessarily want to expose to the Web code that's capable of making radical changes. However, run from the command line, using a normal user account, the script can be kept offline while providing you the necessary rights to copy files, change permissions and so on.
The advantage of command line scripts is that they're usually easier to develop and, more importantly, quicker to use then Web-based of desktop GUIs. As such, they can also play a big part in your development process, helping you perform "build" tasks quickly and easily, such as generating API documentation with phpDocumentor [4] or running your Simple Test [5] suite. Phing [6] (a build tool based on Apache's ANT) and rephlux [7] (a continuous integration tool based on CruiseControl [8]) provide frameworks to help you automate your development process.
Setting Up
I've already covered the basics of setting up PHP so you can run it from the command line in Getting Started with PEAR, Page 2 [9]. It's also worth being aware of what the manual has to say about Using PHP from the command line [10].
When writing your own command line scripts (as opposed to using the PEAR Package Manager), you need to be conscious of which PHP binary you're using (your host, for example, may have set you up with the CGI binary).
A quick way to test this is type the following from the command line.
$ php -v
This should result in something like the following message:
PHP 4.3.6 (cli) (built: Apr 19 2004 10:02:14)
Copyright (c) 1997-2004 The PHP Group
Zend Engine v1.3.0, Copyright (c) 1998-2004 Zend Technologies
Note the (cli) above -- if instead you see "(cgi)", don't worry. The CGI binary can be told to behave like the CLI binary when it's used, but you need to be aware there's a difference. I'll look at solving compatibility issues between the two, as well as older PHP versions, at the end of this article.
Hello World
In its most basic form, the CLI interface can be used just like any PHP script. Here's an example:
<?php
print "Hello World!";
?>
Filename: helloworld.php
If you're running on UNIX based system, you first need to make this script executable with:
chmod +x helloworld.php
You can execute this script by typing:
php helloworld.php
You can avoid specifically having to call the php binary first on UNIX-based systems by placing a SheBang [11] right at the start of the script. This "points" the shell at the binary that should be used to execute the script. Here's an example:
#!/usr/local/bin/php
<?php
print "Hello World!";
?>
Note the path to your PHP executable may vary, /usr/bin/php being another common location.
I can now execute this script with:
$ helloworld.php
On UNIX-based systems, you may also consider dropping the .php file extension, as it's not required in order to execute the script, and because there is some benefit to be had by doing so; if, later, you choose to replace it with a script that's written in another language, while maintaining the functionality, there'll be no confusion.
On Windows, you can achieve almost the same thing by associating the PHP file extension with the PHP binary. You'll find this explained at the end of Replacing Perl Scripts with PHP Scripts [12].
Here, I'll stick to using a .php extension, and I'll assume you'll be executing the scripts by first calling the PHP binary, as this makes the most effective cross-platform approach.
Stream In, Stream Out
When it comes to displaying output from your command line scripts, using the normal echo [13] or print [14] commands is not the best way to go, for two reasons.
First, on Windows, see what happens when you execute the following from a command prompt:
<?php
$i = 0;
while ( $i < 10 ) {
print $i."\n";
sleep(1);
$i++;
}
?>
Filename: printproblem.php
Instead of seeing the numbers displayed at the moment they are printed, with one-second intervals between them, they're all flushed out at once when the script terminates (note that running the same script on Linux results in the numbers being displayed as they happen).
The other reason is more a question of good practice. PHP (the underlying shell, in fact) allows you to direct normal output that's meant for the user to what's known as the "standard out", while diverting any error messages to the "standard error". The advantage in making this division is that it allows you to log errors separately from messages generated by the normal, smooth operation of the script, which can be very useful when running batch jobs.
Part of PHP's CLI interface are three "streams" with which you can interact in more or less the same way as you would a file resource returned from fopen() [15]. The streams are identified with the strings:
php://stdin (read)
php://stdout (write)
php://stderr (write)
With the PHP 4.3.0+ CLI binary, these three streams are automatically available, identified with the constants STDIN, STDOUT and STDERR respectively. Here's how I can use the STDOUT to fix the above script so that it behaves correctly on Windows:
<?php
$i = 0;
while ( $i < 10 ) {
// Write some output
fwrite(STDOUT, $i."\n");
sleep(1);
$i++;
}
?>
Filename: streamout.php
The count should now be displayed at the moment at which fwrite() [16] is called.
Note that, on Windows, PHP takes care of linefeed characters for you, so that they display correctly (you weren't trying to use <br>, were you?).
To be able to read input from the script's user, you can use STDIN combined with fgets() [17], fread() [18], fscanf() [19] or fgetc() [20]. For example:
<?php
fwrite(STDOUT, "Please enter your name\n");
// Read the input
$name = fgets(STDIN);
fwrite(STDOUT, "Hello $name");
// Exit correctly
exit(0);
?>
Filename: nameplease.php
What happens when the script is executed? Well, when the interpreter reaches the fgets() command (it sees the code reading from STDIN), execution pauses and waits for the user to hit enter. On pressing return, fgets() receives everything the user typed from the point at which execution paused, until the moment he or she pressed return. This includes the return linefeed character -- see what happens if you display the contents of the $name variable after passing it through nl2br() [21].
This "pause / continue" behaviour is actually controlled by the terminal (the command prompt). It works on a line-by-line basis and is known as Canonical Mode Input Processing [22].
Note that, on UNIX only, it's possible to have your script process input in Non-canonical Mode [23] using the pcntl_signal() [24], which allows you to respond directly to particular key sequences.
Note also that I've started to use the exit() [25] command at the end of the script, passing it an integer value. This is another question of "good manners". Common practice on UNIX-based systems is for a script to return a status code when execution halts, zero being the standard for denoting the "All OK". Other codes (integers between 1 and 254) are used to identify problems that caused the script to terminate prematurely (it's up to you to define the error codes for you script, which is commonly achieved by defining a set of constants at the start). If another command line script (perhaps written in another language) is used to execute your script, it will rely on the status code returned from exit() to determine whether you script ran successfully or not. For some more thoughts on the subject, see the Advanced Bash-Scripting Guide [26] on Exit and Exit Status [27].
Multiple Choice
You can also use STDIN to handle choices:
<?php
fwrite(STDOUT, "Pick some colors (enter the letter and press return)\n");
// An array of choice to color
$colors = array (
'a'=>'Red',
'b'=>'Green',
'c'=>'Blue',
);
fwrite(STDOUT, "Enter 'q' to quit\n");
// Display the choices
foreach ( $colors as $choice => $color ) {
fwrite(STDOUT, "\t$choice: $color\n");
}
// Loop until they enter 'q' for Quit
do {
// A character from STDIN, ignoring whitespace characters
do {
$selection = fgetc(STDIN);
} while ( trim($selection) == '' );
if ( array_key_exists($selection,$colors) ) {
fwrite(STDOUT, "You picked {$colors[$selection]}\n");
}
} while ( $selection != 'q' );
exit(0);
?>
Filename: pickacolor.php
The end user sees something like this:
Pick some colors (enter the letter and press return)
Enter 'q' to quit
a: Red
b: Green
c: Blue
b
You picked Green
c
You picked Blue
q
Using a loop, I can get my script to continue execution until the user enters 'q' to quit. Another approach would be a while loop that continues forever, but contains a break [28] instruction when some condition is met. It may take you some time to get familiar with this, particularly if you've been using PHP in a Web application in which execution normally halts automatically after 30 seconds, and user input is provided in a single bundle at the start of a request. The main thing is to pay close attention to your code when you insert "never ending" loops like this, so you're not inadvertently executing a database query over and over, for example.
Now, let's get back to the example above. Have a look at what's happening here:
// A character from STDIN, ignoring whitespace characters
do {
$selection = fgetc(STDIN);
} while ( trim($selection) == '' );
The fgetc() function pulls a single character of the STDIN stream that's coming from the user. When execution reaches this point, there's a pause while the terminal waits for the user to enter a character and press return. Both the character I enter, such as the letter 'a', and the new line character are placed on the STDIN stream. Both will eventually be read by fgetc(), but on consecutive loop iterations. As a result, I need to ignore the new line characters which, in this case, I have done using the trim() [29] function, comparing what it returns to an empty string.
If that doesn't make sense, try commenting out the do { and } while ( trim($selection) == '' ); lines in this inner loop, before and after fgetc(). Then, see what happens when you run the script.
Also, right now, users of this script can enter multiple characters between presses of the return key. See if you can work out a way to prevent that by confirming that return was pressed between the user's choices.
Errors and Pipes
Earlier on, I mentioned that you could write errors to a different output location defined by the STDERR constant in PHP. This is achieved in the same way as writing to STDOUT:
<?php
// A constant to be used as an error return status
define ('DB_CONNECTION_FAILED',1);
// Try connecting to MySQL
if ( !@mysql_connect('localhost','user','pass') ) {
// Write to STDERR
fwrite(STDERR,mysql_error()."\n");
exit(DB_CONNECTION_FAILED);
}
fwrite(STDOUT,"Connected to database\n");
exit(0);
?>
Filename: failedconnection.php
Any connection errors will now be reported using STDERR. What this script doesn't do is demonstrate why this functionality can be useful. For that, consider the following:
<?php
// A custom error handler
function CliErrorHandler($errno, $errstr, $errfile, $errline) {
fwrite(STDERR,"$errstr in $errfile on $errline\n");
}
// Tell PHP to use the error handler
set_error_handler('CliErrorHandler');
fwrite(STDOUT,"Opening file foobar.log\n");
// File does not exist - error is generated
if ( $fp = fopen('foobar.log','r') ) {
// do something with the file here
fclose($fp);
}
fwrite(STDOUT,"Job finished\n");
exit(0);
?>
Filename: outwitherrors.php
Now, when you execute this script normally (assuming the file foobar.log doesn't exist in the same directory as the script), it produces output like this:
Opening file foobar.log
fopen(foobar.log): failed to open stream: No such file or directory in /home/harryf/outwitherrors.php on 11
Job finished
The error messages are mixed with the normal output as before. But by piping the output from the script, I can split the errors from the normal output:
$ php outwitherrors.php 2> errors.log
This time, you'll only see these messages:
Opening file foobar.log
Job finished
But, if you look into the directory in which you ran the script, a new file called errors.log will have been created, containing the error message. The number 2 is the command line handle used to identify STDERR. Note that 1 is handle for STDOUT, while 0 is the handle for STDERR. Using the > symbol from the command line, you can direct output to a particular location.
Although this may not seem very exciting, it's a very handy tool for system administration. A simple application, running some script from cron, would be the following:
$ php outwitherrors.php >> transaction.log 2>> errors.log
Using '>>', I tell the terminal to append new messages to the existing log (rather than overwrite it). The normal operational messages are now logged to transaction.log, which I might peruse once a month, just to check that everything's OK. Meanwhile, any errors that need a quicker response end up in errors.log, which some other cron job might email me on a daily basis (or more frequently) as required.
There's one difference between the UNIX and Windows command lines, when it comes to piping output, of which you should be aware. On UNIX, you can merge the STDOUT and STDERR streams to a single destination, for example:
$ php outwitherrors.php > everything.log 2>&1
What this does is re-route the STDERR to STDOUT, meaning that both get written to the log file everything.log.
Unfortunately, Windows doesn't support this capability, although you can find tools (like StdErr [30] that can help you achieve the same ends.
In general, the subject of piping IO is one that I can't do full justice to. On UNIX, it's almost a (black?) art, and usually comes into play when you start using other command line tools like sed, awk and xargs (O'Reilly have dedicated entire books to the subject, including the Loris Book [31]). What you should be getting a feeling for, however, is that, by conforming to the standard conventions for shell scripting, you have the chance to tap into a powerful system administration "framework" that provides all sorts of other tools.
Coping with Arguments
You've already seen some examples of how to read user input from STDIN, once a script has already begun execution. What about being able to pass information to the script at the point it is executed?
If you've ever programmed in a language like C or Java, you'll no doubt be familiar with variables called "argc" and "argv". With PHP, the same naming convention is used, with the integer variable $argc containing the number of arguments passed to the script, while $argv contains an indexed array of the arguments, the "delimiter" between each argument being a space character.
To see how they work, try the following script:
<?php
// Correct English grammar...
$argc > 1 ? $plural = 's' : $plural = '';
// Display the number of arguments received
fwrite(STDOUT,"Got $argc argument$plural\n");
// Write out the contents of the $argv array
foreach ( $argv as $key => $value ) {
fwrite(STDOUT,"$key => $value\n");
}
?>
Filename: arguments.php
I execute this script as normal:
$ php arguments.php
The result is:
Got 1 argument
0 => arguments.php
The first and only argument is the name of the script being executed.
Now, if I execute the following:
$ php arguments.php Hello World!
The code returns:
Got 3 arguments
0 => arguments.php
1 => Hello
2 => World!
The strings "Hello" and "World!" are treated as two separate arguments.
If, instead, I use quotes:
$ php arguments.php "Hello World!"
The output is as follows:
Got 2 arguments
0 => arguments.php
1 => Hello World!
As you can see, passing basic arguments to a PHP script in this manner is very easy. Of course, there are always a few "gotchas" to watch out for.
The variables $argc and $argv are made available in PHP's global scope to those using the PHP 4.3.0+ version of the CLI binary. They are not available outside the global scope (such as inside a function). When you use a CGI binary with register_globals off, $argc and $argv will not be available at all. Instead, it's best to access them via the $_SERVER array you're well used to, using $_SERVER['argv'] and $_SERVER['argc']. They will be available here irrespective of SAPI and register_globals, so long as (wait for it...) the 'register_argc_argv' ini setting is switched on in php.ini (which, thankfully, it has been, by default, since PHP 4.0.0).
The other main "gotcha" is that first argument you saw above -- the name of the script itself. This happened because I named, in the command line, the PHP binary to execute the script, so arguments are calculated relative to the PHP binary, not to the script it's executing. Meanwhile, as you saw above, it's possible to execute a script directly by identifying the binary with the "SheBang" on UNIX, or by file association on Windows. Doing so will mean the script's name no longer appears in the list of arguments. In other words, you need to be careful to check what the first argument contains and whether it matches the contents of $_SERVER['SCRIPT_NAME']. Of course, life would be too easy if $_SERVER['SCRIPT_NAME'] was always available, which it generally won't be if you're using the CGI binary. A more reliable mechanism is to compare the contents of the __FILE__ constant with the contents of $_SERVER[argv][0]:
<?php
if ( realpath($_SERVER['argv'][0]) == __FILE__ ) {
fwrite(STDOUT,'You executed "$ php whereami.php"');
} else {
fwrite(STDOUT,'You executed "$ whereami.php"');
}
?>
Filename: whereami.php
If you've had any experience executing commands using a UNIX shell, you'll have no doubt run into the use of command line options such as:
$ ls -lt
This lists the contents of a directory in "long" format, the files being ordered by modification time. Another example you may know is as follows:
$ mysql --user=harryf --password=secret
The above is used to log in to MySQL.
These are known as command line options, the first example having the "short option" syntax, while the second uses the "long option" syntax.
If I try passing arguments like this to my arguments.php script above:
$ php arguments.php --user=harry --password=secret
I get:
Got 3 arguments
0 => arguments.php
1 => --user=harry
2 => --password=secret
The $argv array is ignorant to the options syntax, restricting itself to breaking up arguments using the space character between them. It's up to you to parse the contents of each argument to handle "options" like this within your script.
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 [32] 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 [33] 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() [34] 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.
[1] http://www.sitepoint.com/article/getting-started-with-pear
[2] http://www.sitepoint.com/examples/phpcli/phpcli1_code.zip
[3] http://www.unixgeeks.org/security/newbie/unix/cron-1.html
[4] http://www.phpdoc.org
[5] http://www.lastcraft.com/simple_test.php
[6] http://phing.info/wiki/index.php
[7] http://sourceforge.net/projects/rephlux
[8] http://cruisecontrol.sourceforge.net/
[9] http://www.sitepoint.com/article/getting-started-with-pear/2
[10] http://www.php.net/manual/en/features.commandline.php
[11] http://c2.com/cgi/wiki?SheBang
[12] http://www.phpbuilder.com/columns/jayesh20021111.php3
[13] http://www.php.net/echo
[14] http://www.php.net/print
[15] http://www.php.net/fopen
[16] http://www.php.net/fwrite
[17] http://www.php.net/fgets
[18] http://www.php.net/fread
[19] http://www.php.net/fscanf
[20] http://www.php.net/fgetc
[21] http://www.php.net/nl2br
[22] http://www.opengroup.org/onlinepubs/007908799/xbd/termios.html#tag_008_001_006
[23] http://www.opengroup.org/onlinepubs/007908799/xbd/termios.html#tag_008_001_007
[24] http://www.php.net/pcntl_signal
[25] http://www.php.net/exit
[26] http://www.faqs.org/docs/abs/HTML/
[27] http://www.faqs.org/docs/abs/HTML/exit-status.html
[28] http://www.php.net/break
[29] http://www.php.net/trim
[30] http://www.teaser.fr/~amajorel/stderr/
[31] http://www.oreilly.com/catalog/sed2/colophon.html
[32] http://pear.php.net/Console_Getopt
[33] http://www.sitepoint.com/article/getting-started-with-pear
[34] http://www.php.net/print_r
SitePoint Marketplace
Buy and sell Websites, templates, domain names, hosting, graphics and more.
Download sample chapters of any of our popular books.

Harry has been working in corporate IT since 1994, with everything from start-ups to Fortune 100 companies. Outside of office hours he runs

