Am trying to run php Interactive shell from a php script. To be more specific, I want to be able to call my classes from interactive shell.
I manage to find this
# custom_interactive_shell.php
function proc($command, &$return_var = null, array &$stderr_output = null)
{
$return_var = null;
$stderr_output = [];
$descriptorspec = [
// Must use php://stdin(out) in order to allow display of command output
// and the user to interact with the process.
0 => ['file', 'php://stdin', 'r'],
1 => ['file', 'php://stdout', 'w'],
2 => ['pipe', 'w'],
];
$pipes = [];
$process = #proc_open($command, $descriptorspec, $pipes);
if (is_resource($process)) {
// Loop on process until it exits normally.
do {
$status = proc_get_status($process);
// If our stderr pipe has data, grab it for use later.
if (!feof($pipes[2])) {
// We're acting like passthru would and displaying errors as they come in.
$error_line = fgets($pipes[2]);
echo $error_line;
$stderr_output[] = $error_line;
}
} while ($status['running']);
// According to documentation, the exit code is only valid the first call
// after a process is finished. We can't rely on the return value of
// proc_close because proc_get_status will read the exit code first.
$return_var = $status['exitcode'];
proc_close($process);
}
}
proc('php -a -d auto_prepend_file=./vendor/autoload.php');
But its just not working, it tries to be interactive but freezes up a lot, and even after the lag it doesn't really execute commands properly.
Example:
> php custom_interactive_shell.php
Interactive shell
php > echo 1;
Warning: Use of undefined constant eo - assumed 'eo' (this will throw an Error in a future version of PHP) in php shell code on line 1
If you want to be able to run your PHP classes from an interactive shell then you can use the default one that ships with Terminal. From the terminal just type:
php -a
Then, in the below example I had created a file called Agency.php that had class Agency in it. I was able to require_once() it into the active shell and then call the class and its methods:
Interactive shell
php > require_once('Agency.php');
php > $a = new Agency();
php > $a->setname("some random name");
php > echo $a->getname();
some random name
You can also use the following in the interactive shell to autoload the files / classes in the current directory:
spl_autoload_register(function ($class_name) {
include $class_name . '.php';
});
Related
I want to run several commands via exec() but I don't want any output to the screen. I do, however, want to hold on to the output so that I can control verbosity as my script runs.
Here is my class:
<?php
class System
{
public function exec($command, array &$output = [])
{
$returnVar = null;
exec($command, $output, $returnVar);
return $returnVar;
}
}
The problem is, most applications put an irritating amount of irrelevant stuff into stderr, which I don't seem to be able to block. For example, here's the output from running git clone through it:
Cloning into '/tmp/directory'...
remote: Counting objects: 649, done.
remote: Compressing objects: 100% (119/119), done.
remote: Total 649 (delta 64), reused 0 (delta 0), pack-reused 506
Receiving objects: 100% (649/649), 136.33 KiB | 0 bytes/s, done.
Resolving deltas: 100% (288/288), done.
Checking connectivity... done.
I've seen other questions claim that using the output buffer can work, however it doesn't seem to work
<?php
class System
{
public function exec($command, array &$output = [])
{
$returnVar = null;
ob_start();
exec($command, $output, $returnVar);
ob_end_clean();
return $returnVar;
}
}
This still produces the same results. I can fix the problem by routing stderr to stdout in the command, however, not only does this prevent me from differentiating from stdout and stderr, this application is designed to run in Windows and Linux, so this is now an unholy mess.
<?php
class System
{
public function exec($command, array &$output = [])
{
$returnVar = null;
// Is Windows
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
exec($command, $output, $returnVar);
return $returnVar;
}
// Is not windows
exec("({$command}) 2>&1", $output, $returnVar);
return $returnVar;
}
}
Is there a way to capture and suppress both stderr and stdout separately?
Update / Answer example
On the advice of #hexasoft in the comments, I updated my method to look like this:
<?php
class System
{
public function exec($command, &$stdOutput = '', &$stdError = '')
{
$process = proc_open(
$command,
[
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
],
$pipes
);
if (!is_resource($process)) {
throw new \RuntimeException('Could not create a valid process');
}
// This will prevent to program from continuing until the processes is complete
// Note: exitcode is created on the final loop here
$status = proc_get_status($process);
while($status['running']) {
$status = proc_get_status($process);
}
$stdOutput = stream_get_contents($pipes[1]);
$stdError = stream_get_contents($pipes[2]);
proc_close($process);
return $status['exitcode'];
}
}
This technique opens up much more advanced options, including asynchronous processes.
Command proc_exec() allows to deal with file descriptors of the exec-ed command, using pipes.
The function is: proc_open ( string $cmd , array $descriptorspec , array &$pipes […optional parameters] ) : resource
You give you command in $cmd (as in exec) and you give an array describing the filedescriptors to "install" for the command. This array is indexed by filedescriptor number (0=stdin, 1=stdout…) and contains the type (file, pipe) and the mode (r/w…) plus the filename for file type.
You then get in $pipes an array of filedescriptors, which can be used to read or write (depending of what requested).
You should not forget to close these descriptors after usage.
Please refer to PHP manual page (and in particular the examples): https://php.net/manual/en/function.proc-open.php
Note that read/write is related to the spawned command, not the PHP script.
How to get the cli command that was run, from PHP?
I have this logging class
class Logger {
public function log($logMessage)
{
$this->writeLog($this->getCommand() . ' : ' . $logMessage);
}
private function getCommand()
{
//How to get the command?
//here var_dump($argv); returns NULL
}
}
This logger is being used by many classes that are run from several commands.
Tow examples:
process_users.php
$logger = new Logger();
$users = new Users();
foreach ($users->getUnprocessed() as $user) {
$logger->log('processing '.$user->getId());
if ($user->process()){
$logger->log('processed ' . $user->getId());
} else {
$logger->log('failed ' . $user->getId());
}
}
upload_consumer.php
$logger = new Logger();
$consumer = new UploadConsumer();
while ($message = $consumer->getNextMessage()){
$logger->log('Uploading ' . $message['name']);
$this->upload($message);
$logger->log('Finished uploading '. $message['name']);
}
So when I run php process_users.php corporate --limit 10
I want to have logs like:
php process_users.php corporate --limit 10 : processing 2554
php process_users.php corporate --limit 10 : processed 2554
php process_users.php corporate --limit 10 : processing 2555
php process_users.php corporate --limit 10 : failed 2555
And when I run php upload_consumer.php -m 100 -w
I want to have logs like:
php upload_consumer.php -m 100 -w : Uploading 564sdf564sdf56sd4f.png
php upload_consumer.php -m 100 -w : Finished uploading 564sdf564sdf56sd4f.png
php upload_consumer.php -m 100 -w : Uploading sd56f4sd54f6sd6f54.png
php upload_consumer.php -m 100 -w : Finished uploading sd56f4sd54f6sd6f54.png
Is this possible?
use $argv which will give you the arguments passed to the script and the script name try var_dump($argv); so you can see what data you're working with.
More info here http://php.net/manual/en/reserved.variables.argv.php
You can also use getopt() if you just want the cli arguments and not the script name
Remember when using within a class or a function then you will have to do global $argv to bring it within the scope of the class or the function. Alternatively you can pass it in as a parameter rather than using global.
My intention is this.
My client.html calls a php script check.php via ajax. I want check.php to check if another script task.php is already being run. If it is, I do nothing. If it is not, I need to run it in the background.
I have an idea what I want to do, but am unsure how to do it.
Part A. I know how to call check.php via ajax.
Part B. In check.php I might need to run task.php. I think I need something like:
$PID = shell_exec("php task.php > /dev/null & echo $!");
I think the "> /dev/null &" bit tells it to run in the background, but am unsure what the "$!" does.
Part C. The $PID I need as a tag of the process. I need to write this number (or whatever) to a file in the same directory, and need to read it every call to check.php. I can't work out how to do that. Could someone give me a link of how to read/write a file with a single number in to the same directory?
Part D. Then to check if the last launched task.php is still running I am going to use the function:
function is_process_running($PID)
{
exec("ps $PID", $ProcessState);
return(count($ProcessState) >= 2);
}
I think that is all the bits I need, but as you can see I am unsure on how to do a few of them.
I would use an flock() based mechanism to make sure that task.php runs only once.
Use a code like this:
<?php
$fd = fopen('lock.file', 'w+');
// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
// run your code
sleep(10);
// ...
flock($fd, LOCK_UN);
} else {
echo 'already running';
}
fclose($fd);
Also note that flock() is, as the PHP documentation points out, portable across all supported operating systems.
!$
gives you the pid of the last executed program in bash. Like this:
command &
pid=$!
echo pid
Note that you will have to make sure your php code runs on a system with bash support. (Not windows)
Update (after comment of opener).
flock() will work on all operating systems (As I mentioned). The problem I see in your code when working with windows is the !$ (As I mentioned ;) ..
To obtain the pid of the task.php you should use proc_open() to start task.php. I've prepared two example scripts:
task.php
$fd = fopen('lock.file', 'w+');
// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
// your task's code comes here
sleep(10);
// ...
flock($fd, LOCK_UN);
echo 'success';
$exitcode = 0;
} else {
echo 'already running';
// return 2 to let check.php know about that
// task.php is already running
$exitcode = 2;
}
fclose($fd);
exit($exitcode);
check.php
$cmd = 'php task.php';
$descriptorspec = array(
0 => array('pipe', 'r'), // STDIN
1 => array('pipe', 'w'), // STDOUT
2 => array('pipe', 'w') // STDERR
);
$pipes = array(); // will be set by proc_open()
// start task.php
$process = proc_open($cmd, $descriptorspec, $pipes);
if(!is_resource($process)) {
die('failed to start task.php');
}
// get output (stdout and stderr)
$output = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);
do {
// get the pid of the child process and it's exit code
$status = proc_get_status($process);
} while($status['running'] !== FALSE);
// close the process
proc_close($process);
// get pid and exitcode
$pid = $status['pid'];
$exitcode = $status['exitcode'];
// handle exit code
switch($exitcode) {
case 0:
echo 'Task.php has been executed with PID: ' . $pid
. '. The output was: ' . $output;
break;
case 1:
echo 'Task.php has been executed with errors: ' . $output;
break;
case 2:
echo 'Cannot execute task.php. Another instance is running';
break;
default:
echo 'Unknown error: ' . $stdout;
}
You asked me why my flock() solution is the best. It's just because the other answer will not reliably make sure that task.php runs once. This is because the race condition I've mentioned in the comments below that answer.
You can realize it, using lock file:
if(is_file(__DIR__.'/work.lock'))
{
die('Script already run.');
}
else
{
file_put_contents(__DIR__.'/work.lock', '');
// YOUR CODE
unlink(__DIR__.'/work.lock');
}
Too bad I didn't see this before it was accepted..
I have written a class to do just this. ( using file locking ) and PID, process ID checking, on both windows and Linux.
https://github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php
I think your are really overdoing it with all the processes and background checks. If you run a PHP script without a session, then you are already essentially running it in the background. Because it will not block any other request from the user. So make sure you don't call session_start();
Then the next step would be to run it even when the user cancels the request, which is a basic function in PHP. ignore_user_abort
Last check is to make sure it's only runs once, which can be easily done with creating a file, since PHP doesnt have an easy application scope.
Combined:
<?php
// Ignore user aborts and allow the script
// to run forever
ignore_user_abort(true);
set_time_limit(0);
$checkfile = "./runningtask.tmp";
//LOCK_EX basicaly uses flock() to prevents racecondition in regards to a regular file open.
if(file_put_contents($checkfile, "running", LOCK_EX)===false) {
exit();
}
function Cleanup() {
global $checkfile;
unlink($checkfile);
}
/*
actual code for task.php
*/
//run cleanup when your done, make sure you also call it if you exit the code anywhere else
Cleanup();
?>
In your javascript you can now call the task.php directly and cancel the request when the connection to the server has been established.
<script>
function Request(url){
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
} else{
return false;
}
httpRequest.onreadystatechange = function(){
if (httpRequest.readyState == 1) {
//task started, exit
httpRequest.abort();
}
};
httpRequest.open('GET', url, true);
httpRequest.send(null);
}
//call Request("task.php"); whenever you want.
</script>
Bonus points: You can have the actual code for task.php write occasional updates to $checkfile to have a sense of what is going on. Then you can have another ajax file read the content and show the status to the user.
Lets make the whole process from B to D simple
Step B-D:
$rslt =array(); // output from first exec
$output = array(); // output of task.php execution
//Check if any process by the name 'task.php' is running
exec("ps -auxf | grep 'task.php' | grep -v 'grep'",$rslt);
if(count($rslt)==0) // if none,
exec('php task.php',$output); // run the task,
Explanation:
ps -auxf --> gets all running processes with details
grep 'task.php' --> filter the process by 'task.php' keyword
grep -v 'grep' --> filters the grep process out
NB:
Its also advisable to put the same check in task.php file.
If task.php is executed directly via httpd (webserver), it will only be displayed as a httpd process and cannot be identified by 'ps' command
It wouldn't work under load-balanced environment. [Edited: 17Jul17]
You can get an exclusive lock on the script itself for the duration of the script running
Any other attempts to run it will end as soon as the lock() function is invoked.
//try to set a global exclusive lock on the file invoking this function and die if not successful
function lock(){
$file = isset($_SERVER['SCRIPT_FILENAME'])?
realpath($_SERVER['SCRIPT_FILENAME']):
(isset($_SERVER['PHP_SELF'])?realpath($_SERVER['PHP_SELF']):false);
if($file && file_exists($file)){
//global handle stays alive for the duration if this script running
global $$file;
if(!isset($$file)){$$file = fopen($file,'r');}
if(!flock($$file, LOCK_EX|LOCK_NB)){
echo 'This script is already running.'."\n";
die;
}
}
}
Test
Run this in one shell and try to run it in another while it is waiting for input.
lock();
//this will pause execution until an you press enter
echo '...continue? [enter]';
$handle = fopen("php://stdin","r");
$line = fgets($handle);
fclose($handle);
I have a script that is designed to be run both as a web page, and via the console.
Detecting which method was used to invoke the script seems pretty straight forward, but when the script is being run from the console, I need to know if the script is being run interactively or not (user typing commands, or input redirected from a file).
php script.php
versus
php script.php < input_file
Is this possible?
I also needed a slightly more flexible solution than posix_isatty that could detect:
Is the script being run from the terminal
Is the script receiving data via a pipe or from a file
Is the output being redirected to a file
After a bit of experimenting and digging around through libc headers, I came up with a very simple class that can do all of the above and more.
class IOMode
{
public $stdin;
public $stdout;
public $stderr;
private function getMode(&$dev, $fp)
{
$stat = fstat($fp);
$mode = $stat['mode'] & 0170000; // S_IFMT
$dev = new StdClass;
$dev->isFifo = $mode == 0010000; // S_IFIFO
$dev->isChr = $mode == 0020000; // S_IFCHR
$dev->isDir = $mode == 0040000; // S_IFDIR
$dev->isBlk = $mode == 0060000; // S_IFBLK
$dev->isReg = $mode == 0100000; // S_IFREG
$dev->isLnk = $mode == 0120000; // S_IFLNK
$dev->isSock = $mode == 0140000; // S_IFSOCK
}
public function __construct()
{
$this->getMode($this->stdin, STDIN);
$this->getMode($this->stdout, STDOUT);
$this->getMode($this->stderr, STDERR);
}
}
$io = new IOMode;
Some example usage, to show what it can detect.
Input:
$ php io.php
// Character device as input
// $io->stdin->isChr == true
$ echo | php io.php
// Input piped from another command
// $io->stdin->isFifo == true
$ php io.php < infile
// Input from a regular file (name taken verbatim from C headers)
// $io->stdin->isReg == true
$ mkdir test
$ php io.php < test
// Directory used as input
// $io->stdin->isDir == true
Output:
$ php io.php
// $io->stdout->isChr == true
$ php io.php | cat
// $io->stdout->isFifo == true
$ php io.php > outfile
// $io->stdout->isReg == true
Error:
$ php io.php
// $io->stderr->isChr == true
$ php io.php 2>&1 | cat
// stderr redirected to stdout AND piped to another command
// $io->stderr->isFifo == true
$ php io.php 2>error
// $io->stderr->isReg == true
I've not included examples for links, sockets, or block devices, but there's no reason they shouldn't work, as the device mode masks for them are in the class.
(Not tested on Windows - mileage may vary)
posix_isatty()
if (posix_isatty(0)) {
// STDIN is a TTY
} else {
// STDIN is a pipe or has no associated TTY
}
Obviously this only works on POSIX compliant operating systems, where PHP has the posix extension installed. I am not aware of a Windoze equivalent.
For Windows, you can use this:
$b = stream_isatty(STDIN);
if ($b) {
echo "php script.php\n";
} else {
echo "php script.php < input_file\n";
}
https://php.net/function.stream-isatty
I'm using proc_open in php to launch a subprocess and send data back and forth.
At some point I'd like to wait for the process to end and retrieve the exit code.
The problem is that if the process has already finished, my call to proc_close returns -1. There is apparently much confusion over what proc_close does actually return and I haven't found a way to reliably determine the exit code of a process opened with proc_open.
I've tried using proc_get_status, but it seems to also return -1 when the process has already exited.
Update
I can't get proc_get_status to ever give me a valid exit code, no matter how or when it is called. Is it broken completely?.
My understanding is that proc_close will never give you a legit exit code.
You can only grab the legit exit code the first time you run proc_get_status after the process has ended. Here's a process class that I stole off the php.net user contributed notes. The answer to your question is in the is_running() method:
<?php
class process {
public $cmd = '';
private $descriptors = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w')
);
public $pipes = NULL;
public $desc = '';
private $strt_tm = 0;
public $resource = NULL;
private $exitcode = NULL;
function __construct($cmd = '', $desc = '')
{
$this->cmd = $cmd;
$this->desc = $desc;
$this->resource = proc_open($this->cmd, $this->descriptors, $this->pipes, NULL, $_ENV);
$this->strt_tm = microtime(TRUE);
}
public function is_running()
{
$status = proc_get_status($this->resource);
/**
* proc_get_status will only pull valid exitcode one
* time after process has ended, so cache the exitcode
* if the process is finished and $exitcode is uninitialized
*/
if ($status['running'] === FALSE && $this->exitcode === NULL)
$this->exitcode = $status['exitcode'];
return $status['running'];
}
public function get_exitcode()
{
return $this->exitcode;
}
public function get_elapsed()
{
return microtime(TRUE) - $this->strt_tm;
}
}
Hope this helps.
I also was getting unexpected results trying to get the return code via proc_get_status, until I realized I was getting the return code of the last command I had executed (I was passing a series of commands to proc_open, separated by ;).
Once I broke the commands into individual proc_open calls, I used the following loop to get the correct return code. Note that normally the code is executing proc_get_status twice, and the correct return code is being returned on the second execution. Also, the code below could be dangerous if the process never terminates. I'm just using it as an example:
$status = proc_get_status($process);
while ($status["running"]) {
sleep(1);
$status = proc_get_status($process);
}