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 quitn");
// Display the choices
foreach ( $colors as $choice => $color ) {
fwrite(STDOUT, "t$choice: $colorn");
}
// 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 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() 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 databasen");
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 $errlinen");
}
// Tell PHP to use the error handler
set_error_handler('CliErrorHandler');
fwrite(STDOUT,"Opening file foobar.logn");
// File does not exist - error is generated
if ( $fp = fopen('foobar.log','r') ) {
// do something with the file here
fclose($fp);
}
fwrite(STDOUT,"Job finishedn");
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 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). 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$pluraln");
// Write out the contents of the $argv array
foreach ( $argv as $key => $value ) {
fwrite(STDOUT,"$key => $valuen");
}
?>
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.
Frequently Asked Questions about PHP Command Line
What is the PHP command line and why is it important?
The PHP command line is a feature of PHP that allows developers to execute PHP scripts and applications directly from the command line interface (CLI) of a computer system, without needing a web server or browser. This is particularly useful for tasks that need to be automated or scheduled, such as system maintenance tasks, file manipulations, and data processing tasks. It also allows for testing and debugging of PHP scripts in a controlled environment.
How do I start using the PHP command line?
To start using the PHP command line, you first need to ensure that PHP is installed on your system. Once installed, you can access the PHP CLI by typing ‘php’ into your system’s command prompt. This will open the PHP CLI where you can then run your PHP scripts.
What is the difference between PHP CLI and PHP CGI?
PHP CLI and PHP CGI are two different ways of running PHP scripts. PHP CLI is used for running scripts directly from the command line, while PHP CGI is used for running scripts via a web server. The main difference between the two is that PHP CLI does not require a web server to run scripts, making it ideal for tasks that need to be automated or run in the background.
How can I use the ‘header’ function in PHP CLI?
The ‘header’ function in PHP is typically used to send raw HTTP headers to a client or browser. However, in the context of PHP CLI, this function does not have any effect as there is no client or browser involved. Instead, you can use other methods to control the output of your scripts, such as echo or print.
Can I use the ‘system’ function in PHP CLI?
Yes, you can use the ‘system’ function in PHP CLI. This function is used to execute an external program and display the output. It can be very useful in PHP CLI for integrating with other system commands or programs.
How can I handle errors in PHP CLI?
Error handling in PHP CLI can be done in a similar way to how it’s done in a web environment. You can use the ‘set_error_handler’ function to define a custom error handler, or use the ‘@’ operator to suppress error messages.
Can I use PHP CLI to create a web server?
Yes, PHP CLI includes a built-in web server that can be used for testing and development purposes. This can be started by using the ‘php -S’ command followed by the address and port number.
How can I pass arguments to a PHP script in the command line?
You can pass arguments to a PHP script in the command line by including them after the script name. These arguments can then be accessed in the script using the ‘$argv’ array.
Can I use PHP CLI to run scripts in the background?
Yes, you can use PHP CLI to run scripts in the background. This can be done by appending ‘&’ to the end of the command. This will run the script in the background and free up the command line for other tasks.
How can I stop a PHP script running in the command line?
You can stop a PHP script running in the command line by pressing ‘Ctrl + C’. This will send a signal to the script to terminate its execution.
![Harry Fuecks](https://uploads.sitepoint.com/wp-content/uploads/2018/10/1539931198Harry-fuecks-150x150.jpeg)
Harry Fuecks is the Engineering Project Lead at Tamedia and formerly the Head of Engineering at Squirro. He is a data-driven facilitator, leader, coach and specializes in line management, hiring software engineers, analytics, mobile, and marketing. Harry also enjoys writing and you can read his articles on SitePoint and Medium.