Is it possible to create an interactive shell, using PHP alone?
I mean something like you have with databases, Python, etc. If it is, how?
Yes, it's possible. In order to be interactive, the program must be able to wait for and read in user input from stdin. In PHP, you can read from stdin by opening a file descriptor to 'php://stdin'. Taken from an answer to different question, here's an example of an interactive user prompt in PHP (when run from the command line, of course):
echo "Continue? (Y/N) - ";
$stdin = fopen('php://stdin', 'r');
$response = fgetc($stdin);
if ($response != 'Y') {
echo "Aborted.\n";
exit;
}
Of course, to get a full line of input rather than a single character, you'd need fgets() instead of fgetc(). Depending what your program/shell will do, the whole program might be structured as one big continuous loop. Hopefully that gives you an idea how to get started. If you wanted to get really fancy (CLI pseudo-GUI), you could use ncurses.
Since this question has been asked and answered, a better solution has been added to PHP. In all recent PHP versions, at least PHP 5.4, you can easily get user input as so:
$input = fgets(STDIN);
The way I understand your question you just want the PHP interpreter to run on the command line so you that it will evaluate any PHP code that you type. I use that feature of Python all the time to test code snippets. In which case I believe the answer you are looking for is to execute (from the command line):
php -a
Assuming PHP is in your path this will drop you in to the PHP interpreter, like it does on mine:
php -a
Interactive shell
php >
From there you can start to evaluate arbitrary PHP expressions and see the results:
php > $a = 'abcdef';
php > echo strlen($a);
6
Here's an expanded take on this. I've added an isCLI() check in case you're run your script both in CLI and on a web server. Otherwise the server could loop using my function. This solution will prompt the user, check the input, and re-prompt the user for fixed input if necessary.
I rtrim() the input, because if the user uses return to submit their entry, that may be appended to the entry. Validation is not necessary; just don't pass a function in that case.
function isCLI() {
return (php_sapi_name() === 'cli' OR defined('STDIN'));
}
function userPrompt($message, $validator=null) {
if (!isCLI())
return null;
print($message);
$handle = fopen ('php://stdin','r');
$line = rtrim(fgets($handle), "\r\n");
if (is_callable($validator) && !call_user_func($validator, $line)) {
print("Invalid Entry.\r\n");
return userPrompt($message, $validator);
} else {
print("Continuing...\r\n");
return $line;
}
}
// Example =====================
function validateSetLangCode($str) {
return preg_match("/^[A-Z0-9]{3}-[A-Z]{2}$/", $str);
}
$code = userPrompt("Please enter the set / language codes. Use the format 'SET-EN', where SET is the three-letter set code and EN is the two-letter lang code. \r\n", 'validateSetLangCode') ?: 'SET-EN';
var_dump($code);
Since PHP has a built-in Unix-only function readline() to do exactly that, note:
We can use and hold the result of readline in a variable.
#!/usr/bin/php
<?php
$user = readline("List dir [l] | Say hello [h] | exit [q]: ");
if ($user === "l"){ system("ls"); }
if ($user === "h"){ echo "Hello!"; }
if ($user === "q"){ exit; }
echo "\nThanks!";
Example output:
l ls result
h «hello»
q exit
Ctrl + C exit.
Ctrl + D with empty input, continue to the next sequence. «Thanks». $user is defined and empty, no error.
Ctrl + D with some input: No action. Still waiting for input.
Ctrl + M Continue and take the current input in $user.
Ctrl + J Continue and take the current input in $user, same behavior as Ctrl + M.
Return continue to the next sequence «Thanks». $user can stay empty, no error.
Ctrl + Z may be used to cancel a loop and move to the top one. $user will be unset if the var is not defined in this scope.
Depending input, we can define empty values using!empty or do more surgical testings (the readline response can be many chars).
$user can be tested with !isset if not yet asked.
There is also the built-in readline_add_history() to store the user input into an object, where values can be retrieved directly by their name (nice for code clarity):
readline_add_history($user);
print_r(readline_list_history());
print_r(readline_user());
It is very useful to build real complex stuffs!
See how to catch and send POSIX signals.
PHP function readline()
If you want the interactive shell to process PHP commands, one example is phpsh which was apparently created at Facebook, but it is written in Python.
I know the questioner didn't want the second option, but for those that wanted the second option as I did, in addition to phpsh, PHP also has its own shell:
Just run php -a.
Check out Sheldon.
It's pretty easy to get started. It includes Symfony 2 and Zend Framework libraries that do a lot of the basic console I/O work and gives you a higher-level abstraction built around command objects (with regex routes) and Contexts (which hold immutable state).
One of the things I love is that "out of the box", your application can run as either an interactive shell, or as a standard script that you can run from the command line, specify a command, pass any arguments, and when the command is finished the application exits.
Related
For a command line application I am trying to ask a simple question using the following code (example is not real life, code, but resembles the "real" version):
echo "Do you want to quit? [Y]/N";
$handle = fopen ( "php://stdin", "r" );
$input = trim(fgets($handle));
if ( empty($input) ) {
$input = 'Y';
echo "Y\n";
}
The result I want, is the following - When a user does -NOT- provide input:
Do you want to quit? [Y]/N: N // User only hits enter, not 'N'..
What I get is:
Do you want to quit? [Y]/N: // User only hits enter, not 'N'..
N
So the question is: How do I force echo 'Something'; to NOT print a newline after the echo.
Basically I need the equivalent of bash's echo -n '...' (does not print the trailing newline).
To understand why is it so - you need to understand that there are two things: STDIN and STDOUT that are involved into program. And - yes, php does not add new lines. With simple echo "foo"; you'll get exactly "foo", without new line.
But why are you seeing new line then? Simple: because you've pressed "it". That is: you've pressed "enter" key, terminal got it and printed it. Your program, of course, also got it, but at that moment the "key" is already printed.
What can you do? On that step: nothing. It's already done and that's it. You'll see it in your screen. However, yes, there is a trick that I can suggest. You can use stty to maintain behavior, when you can control the input and/or output. Combined with system() you'll get the idea.
Here we are with code:
function emulatePrintable()
{
$result = '';
while($c = trim(fgetc(STDIN)))
{
echo($c);
$result.=$c;
}
return $result;
}
system('stty -echo');
echo("Do you want to quit? [Y]/N ");
$result = emulatePrintable();
if($result==='')
{
echo("You didn't type anything!");
}
echo "\n"; //<--- this is to delimit program's end of work
system('stty echo');
What's happening? You're doing this:
Suppress any input printing with stty -echo. This is the trick. You're suppressing only input display, not output display. That is why you'll be able to see echo() strings from PHP
Emulating output for printable characters. That is: you still want to show what user is typing (your Y or N) - but you want to skip new line. Simple emulatePrintable() will do the work (may be not the best name, but at least I've tried)
After you've got the input (it's interrupted with EOL, for example) - you can examine what is it. If it's an empty string, then you've caught it: user typed nothing.
Now, do not forget to enable input display with stty echo - otherwise.. well, you'll end with "non-working" terminal.
Benefit: with this you'll be able even to decide, to print character or not (for example, to restrict only Y and N for output).
So this is the solution for unix-based OS. In Win - best of luck to you. You may check this page for console modes & related stuff, but I'm not sure it will work (since have not tested).
I have a problem in using system(), passthru() and exec(), the example below will describe it better, I have a string x that I will use it as argument for system():
$x = ' second_string' .$array[$i];
system("cat $x "); // not working
system("cat ".$x); // not working
system("cat { $x }"); // not working
system('cat "' . $x . '" '); // not working
system("cat second_string_xx.txt" ); //working !!
Your question couldn't be more vague but one of your comments suggest that you expect an error message when you feed cat with an invalid file. Well, that's not how cat actually works. As most classic Unix commands, it writes error messages into standard error (stderr) rather than standard output (stdout). You either need to use a PHP function that allows to capture stderr (such as proc_open) or redirect stderr to somewhere else (such as stdout or a file).
Apart from that, PHP program execution functions have many optional parameters and return values and you are ignoring them all. For instance, system() returns the last line of the command output on success (and FALSE on failure) and allows to capture the return value (which will be non-zero in case of error and is the standard mechanism to detect errors).
I'm working on a game, written in PHP and that runs in a console. Think back to old MUDs and other text-based games, even some ASCII art!
Anyway, what I'm trying to do is have things happening while also accepting user input.
For instance, let's say it's a two player game and Player 1 is waiting for Player 2 to make a move. This is easily done by just listening for a message.
But what if Player 1 wants to change some options? What if they want to view details on aspects of the game state? What about conceding the game? There are many things a Player may want to do while waiting for their opponent to make a move.
Unfortunately the best I have right now is the fact that Ctrl+C completely kills the program. The other player is then left hanging, until the connection is dropped. Oh, and the game is completely lost.
I get user input with fgets(STDIN). But this blocks execution until input has been received (which is usually a good thing).
Is it even possible for a console program like this to handle input and output simultaneously? Or should I just look at some other interface?
In short PHP is not built for this, but you might get some help from one of these extensions. I'm not sure how thorough they are, but you really probably want to use a text UI library. (And really you probably do not want to use PHP for this.)
All that said, you need to get non blocking input from STDIN character by character. Unfortunately most terminals are buffered from PHP's point of view, so you won't get anything until enter is pressed.
If you run stty -icanon (or your OS's equivalent) on your terminal to disable buffering, then the following short program basically works:
<?php
stream_set_blocking(STDIN, false);
$line = '';
$time = microtime(true);
$prompt = '> ';
echo $prompt;
while (true)
{
if (microtime(true) - $time > 5)
{
echo "\nTick...\n$prompt$line";
$time = microtime(true);
}
$c = fgetc(STDIN);
if ($c !== false)
{
if ($c != "\n")
$line .= $c;
else
{
if ($line == 'exit' || $line == 'quit')
break;
else if ($line == 'help')
echo "Type exit\n";
else
echo "Unrecognized command.\n";
echo $prompt;
$line = '';
}
}
}
(It relies on local echo being enabled to print the characters as they are typed.)
As you see, we are just looping around forever. If a character exists, add it to the $line. If enter is pressed, process $line. Meanwhile, we are ticking every five seconds just to show that we could be doing something else while we wait for input. (This will consume maximum CPU; you'd have to issue a sleep() to get around that.)
This isn't meant to be a practical example, per se, but perhaps will get you thinking in the proper direction.
It is possible to build a game like you describe using ncurses (non-blocking mode) and libevent. That way, you get close to no CPU consumption. Handling individual keys is sometimes awkward (implement Backspace yourself, it's not fun at all - and did you know various OSes send different keycodes on Backspace press?), and gets really tricky if you want to support UTF-8 properly. Still, completely viable.
In particular, it is beneficial to make extensive use of libevent, by reading both the network and keyboard (stdin) input with it. This function enables you to listen for individual keys:
http://www.php.net/manual/en/function.ncurses-cbreak.php
which you can later read using libevent API. The key to keep in mind is that you will sometimes end up reading more than 1 key at a time, and it has to be handled (so loop over everything that you have read). Otherwise, the user will be annoyed to see that not all key presses are "reaching" the application and some are lost.
Sorry Matthew, I'm going to have to un-accept your answer, because I have found it myself:
Use the following code to receive user input while still doing something else:
while(/* some condition that the code running is waiting on */) {
// perform one step or iteration of that code
exec("choice /N /C ___ /D _ /T _",$out,$ret);
// /C is a list of letters that do something
// /D is the default action that will be used as a no-op
// /T is the amount of time to wait, probably best set to one second
switch($ret) {
// handle cases - the "default" case should be "continue 2"
}
}
This can then be used to interrupt the loop and enter an options menu, or trigger some other event, or could even be used to type out a command if used right.
i've got this snazzy python code:
import subprocess
value = subprocess.Popen(["php","./php/php_runner.php"],stdout=subprocess.PIPE);
the problem is, i have no idea how to check if the php_runner, well, ran. Currently, it has the following salient sections:
if (count($argv) != 4){
die("four arguments are needed\n");
}
and
$returnValue = call_to_another_php_class();
return $returnValue;
So what i want is this:
How do i get the return value, whatever it may be, using python?
You probably are going to tell me to use "PIPE" in the answer, but the (to me, incomprehensible) python docs (http://docs.python.org/library/subprocess.html) state:
Do not use stdout=PIPE or stderr=PIPE with this function. As the pipes are not being read in >the current process, the child process may block if it generates enough output to a pipe to fill up >the OS pipe buffer.
So what do i use then, because while I don't really know what they're barking on about, i sit up and take note about notes in grey boxes. Pity they didn't spell out what i'm meant to do - but, well, what am i meant to do?
the "returnValue" that my php code returns, is that what python is going to pickup as the return value from the function? If not, how do i return that value?
cheers!
UPDATE
Thanks to the given answer, here's the changes i made:
edited /etc/php5/cli/conf.d/mcrypt.ini (actually, this is just a change for ubuntu 10.04, and I changed the first line to begin with a ; instead of a #. That stopped an annoying "we don't like #" error that kept popping up)
in my php, I changed the code to read:
if (count($argv) != 4){
fwrite(STDERR, "four arguments are needed\n");
exit(1); // A response code other than 0 is a failure
}
this puts my error value as an error. the die() command wasn't doing that for me.
changed the python to read:
value = subprocess.Popen(["php","./php/php_runner.php"],stdout=subprocess.PIPE,
stderr=subprocess.PIPE);
print value.communicate();
Yeah, realistically, i'd do an if on value.communicate()[1], becase that is where the errors are.
$returnValue = call_to_another_php_class();
if ($returnValue == 1){ //hah, php has a good return value as 1.
//no problem
} else {
fwrite(STDERR,get_error_from_php_class());
exit(1);
}
booyah!
Since you're using the Popen constructor rather than the call functions, those notes about PIPE don't apply to you.
Use .communicate() as documented to wait for the program to finish and get the output.
I have a program on my linux server that asks the same series of questions each time it executes and then provides several lines of output. My goal is to automate the input and output with a php script.
The program is not designed to accept input on the command line. Instead, the program asks question 1 and waits for an answer from the keyboard, then the program asks question 2 and waits for an answer from the keyboard, etc.
I know how to capture the output in an array by writing:
$out = array();
exec("my/path/program",$out);
But how do I handle the input?
Assume the program asks 3 questions and valid answers are: left 120 n
What is the easiest way using php to pass that input to the program?
Can I do it somehow on the exec line?
I’m not a php noob but simply have never needed to do this before.
Alas, my googling is going in circles.
First up, just to let you know that you're trying to reinvent the wheel. What you're really looking for is expect(1), which is a command-line utility intended to do exactly what you want without involving PHP.
However, if you really want to write your own PHP code you need to use proc_open. Here are some good code examples on reading from STDOUT and writing to STDIN of the child process using proc_open:
http://www.php.net/manual/en/function.proc-open.php#79665
How to pass variables as stdin into command line from PHP
http://camposer-techie.blogspot.com/2010/08/ejecutando-comandos-sobre-un-programa.html (this one is in Spanish, sorry, but the code is good)
Finally, there is also an Expect PECL module for PHP.
Hope this helps.
Just add the arguments to the exec line.
exec("/path/to/programname $arg1 $arg2 $arg3");
... but don't forget to apply escapeshellarg() on every argument! Otherwise, you're vulnerable to injected malicious code.
$out = array();
//add elements/parameters/input to array
string $execpath = "my/path/program ";
foreach($out as $parameter) {
$execpath += $parameter;
//$execpath += "-"+$execpath; use this if you need to add a '-' in front of your parameters.
}
exec($execpath);