This class allows you to implement a shell in 100% PHP. It's also extensible: Write your own commands and register them using registerCommand($command, $callback) function as the example does. It checks if the command is from the system (which takes preference) or from the PHP program, executing it in a transparent way.
Finally, if you aren't using commands from the system nor internal, the code introduced is running using eval().
See the example for more information!
phpshell.class.php
<?php
/*
* PHPSH - An example about shellin', PHP and callbacks
*
* Copyright (C) 2008 Victor Roman Archidona <daijo@daijo.org>
* http://blog.daijo.org
*/
class PHPShell
{
protected $bufferOutput = null;
protected $consolePrompt = "phpsh (%path%)> ";
private $currentPath = null;
private $registered_commands = array();
/*
* @fn __construct($prompt = null)
* @param[in] string $prompt Prompt to be used
*
* This function is the entrypoint for the shell emulator
*/
public function __construct($prompt = null)
{
$this->currentPath = getcwd();
$this->setPrompt($prompt ? $prompt : $this->consolePrompt);
/*
* @fn getCommand($input)
* @brief From a given input, strips the first thing (the command)
* @param[in] string $input Input to analyze
* @return The first thing
*/
function getCommand($input)
{
$input = trim($input);
$space_exists = strpos($input, " ");
if ($space_exists)
$input = substr($input, 0, $space_exists);
return $input;
}
/*
* @fn getParameters($input)
* @brief From a given input, strips the parameters (all except first word)
* @param[in] string $input Input to analyze
* @return Every word, as an array, except the first one
*/
function getParameters($input)
{
$input = trim($input);
$space_exists = strpos($input, " ");
/*
* @fn Process($input)
* @brief Event dispatcher. The core of the class
* @param[in] string $input Input to process
*
* NOTES
* -----
* 1. First gets the command and parameters
* 2. If command is not null:
* 1. If is a system command, it gets the full path and execute
* 2. Else, if is an internal command, runs it
* 3. Otherwise runs the input as php code using eval()
*/
public function Process($input)
{
$command = $this->getCommand($input);
$params = $this->getParameters($input);
if (strlen($command)) {
if ($this->isSystemCommand($command)) {
$full_cmd_path = $this->getSystemCommandFullPath($command);
$this->runSystemCommand($full_cmd_path, $params);
} else if ($this->isCommand($command)) {
$output = $this->runCommand($command, $params);
if ($output)
$this->Write($output);
} else {
eval($input);
}
}
}
public function _builtInCommandChdir($parameters)
{
if ($parameters[0] != DIRECTORY_SEPARATOR)
$newdir = realpath($this->currentPath.DIRECTORY_SEPARATOR.$parameters);
else
$newdir = $this->currentPath.DIRECTORY_SEPARATOR.$parameters;
if (is_dir($newdir)) {
chdir($newdir);
$this->currentPath = getcwd();
} else {
$this->Write("Directory $parameters does not exists".PHP_EOL);
}
}
public function _builtInCommandExit($parameters = null)
{
$exit_code = 0;
if ($parameters[0] && is_numeric($parameters[0]))
$exit_code = $parameters[0];
exit($exit_code);
}
/**
* @fn promptReplace()
* @brief Replaces the macro appearances inside current prompt
*/
private function promptReplace()
{
$prompt = str_replace("%path%", $this->currentPath, $this->consolePrompt);
return $prompt;
}
/**
* @fn setPrompt($prompt)
* @brief sets the prompt to be displayed
* @param[in] string $prompt prompt to be setted
*
* NOTES
* 1. You can use the macro %path% to display the *current* path.
*/
public function setPrompt($prompt)
{
$this->consolePrompt = $prompt;
}
/**
* @fn getPrompt()
* @brief Gets the current prompt
*/
public function getPrompt()
{
return $this->consolePrompt;
}
/**
* @fn waitInput()
* @brief Waits until the user press the enter key, showing the prompt
*/
public function waitInput()
{
$this->Write($this->promptReplace($this->consolePrompt));
return $this->Read();
}
/**
* @fn registerCommand($command, $callback)
* @brief Register a command with an internal callback
* @input[in] string $command Command name exported
* @input[in] string $callback Function that the command will execute
*
* @return true if the command is correctly registered
*
* NOTES:
* 1. Callback must be a function or an array(class,method) pair
* 2. The command must be unique (not previously registered)
*/
public function registerCommand($command, $callback)
{
$command = strtolower($command);
if (is_array($callback)) {
list($_class, $_method) = $callback;
if (!method_exists($_class, $_method)) {
$this->Write("Function callback: \"$_class->$_method\" for command: \"$command\" does not exists\n");
return false;
}
} else {
if (!function_exists($callback)) {
$this->Write("Function callback: \"$callback\" for command: \"$command\" does not exists\n");
return false;
}
}
/**
* @fn isCommand($command)
* @brief Determines if $command was registered via registerCommand
* @input[in] string $command Command to be checked
*
* @return true if $command was registered via registerCommand, false otherwise
*/
public function isCommand($command)
{
foreach ($this->registered_commands as $current)
if ($current['command'] == $command)
return true;
return false;
}
/**
* @fn isSystemCommand($command)
* @brief Determines if $command is from system or not
* @input[in] string $command Command to be checked
*
* @return true if $command came from system, false otherwise
*/
public function isSystemCommand($command)
{
$syspath = $_ENV["PATH"];
foreach (explode(":", $syspath) as $path)
if (is_executable($path.DIRECTORY_SEPARATOR.$command))
return true;
return false;
}
/**
* @fn getSystemCommandFullPath($command)
* @brief Gets the full (real) path from a system command
* @input[in] string $command Command to obtain
*
* @return The full path of the given system command
*/
public function getSystemCommandFullPath($command)
{
$syspath = $_ENV["PATH"];
foreach ((array)explode(":", $syspath) as $path)
if (is_executable($path.DIRECTORY_SEPARATOR.$command))
return $path.DIRECTORY_SEPARATOR.$command;
return null;
}
/**
* @fn runSystemCommand($command, $parameters)
* @brief Runs a system command with specified $parameters
* @param[in] string $command Command to execute
* @param[in] string $parameters Parameters passed to the command
*/
public function runSystemCommand($command, $parameters = null)
{
$parameters = is_array($parameters) ? implode(" ", $parameters) : null;
passthru("$command $parameters");
}
/**
* @fn runCommand($command, $parameters)
* @brief Runs a registered command with specified $parameters
* @param[in] string $command Command to execute
* @param[in] string $parameters Parameters passed to the command
*/
public function runCommand($command, $parameters = null)
{
$callback = $this->getCommandCallback($command);
/**
* @fn getCommandCallback($command)
* @brief Gets the internal callback from an internal command
* @param[in] string $command Command to check
*/
public function getCommandCallback($command)
{
foreach ($this->registered_commands as $current)
if ($current['command'] == $command)
return $current['callback'];
return null;
}
/**
* @fn Read
* @brief Reads input from STDIN
* @return string readed from STDIN, without \r nor \n
*/
public static function Read()
{
$drop_chars = array("\n", "\r");
/**
* @fn Write($text)
* @brief Writes $text to STDOUT
* @param[in] string $text Text to be written
*/
public static function Write($text)
{
$len = strlen($text);
fwrite(STDOUT, $text, $len);
}
}
?>
Exmaple Usage
<?php
# 1. Load the class
require_once "phpshell.class.php";
# 2. Create an instance to it
# 3. Some example commands:
$phpsh = new PHPShell("phpsh (%path%)> ");
$phpsh->registerCommand("show", "_Show");
$phpsh->registerCommand("mem_usage", "_DisplayUsedMemory");
# 4. Main loop (You can replace $phpsh->Process() for your own
# event dispatcher. See phpsh.class.php for details
for (;;) {
$input = trim($phpsh->waitInput());
$phpsh->Process($input);
}
# These functions are examples ones:
function _Show($parameters = array())
{
$file = $parameters;
if (!is_file($file)) {
WriteConsole("File $file does not exists".PHP_EOL);
return 1;
}
if (!is_readable($file)) {
WriteConsole("File $file is not readable".PHP_EOL);
return 1;
}