Execute "less" from command-line PHP w/ Scrolling - php

I want to execute less and similar programs from the command-line in PHP.
I have tried the usual suspects (exec, shell_exec, passthru, etc), and while many of them can dump the file to the screen, the process is terminated before I can make use of it. If I wanted cat, I'd use it.
How do I execute a program in this fashion?

You could use proc_open to feed input to and get output back from a process via pipes. However, it doesn't seem like less allows for user interaction via pipes as it basically degrades to a cat command. Here's my first (failed) approach:
<?php
$dspec = array(
0 = array('pipe', 'r'), // pipe to child process's stdin
1 = array('pipe', 'w'), // pipe from child process's stdout
2 = array('file', 'error_log', 'a'), // stderr dumped to file
);
// run the external command
$proc = proc_open('less name_of_file_here', $dspec, $pipes, null, null);
if (is_resource($proc)) {
while (($cmd = readline('')) != 'q') {
// if the external command expects input, it will get it from us here
fwrite($pipes[0], $cmd);
fflush($pipes[0]);
// we can get the response from the external command here
echo fread($pipes[1], 1024);
}
fclose($pipes[0]);
fclose($pipes[1]);
echo proc_close($proc);
I guess for some commands this approach might actually work - and there are some examples in the php manpage for proc_open that might be helpful to look over - but for less, you get the whole file back and no possibility for interaction, maybe for reasons mentioned by Viper_Sb's answer.
...But it seems easy enough to simulate less if that's all you need. For example, you could read the output of the command into an array of lines and feed it in bite-sized chunks:
<?php
$pid = popen('cat name_of_file_here', 'r');
$buf = array();
while ($s = fgets($pid, 1024))
$buf[] = $s;
pclose($pid);
for ($i = 0; $i < count($buf)/25 && readline('more') != 'q'; $i++) {
for ($j = 0; $j < 25; $j++) {
echo array_shift($buf);
}
}

I don't believe this is possible. PHP is not a VM/shell environment, the commands it has to access other programs all return control to it, and normally there is no interaction while PHP is running.
One last thing, try with the backtick operators, if that doesn't work then I'm pretty sure you can't do this without writing up something yourself that will sleep and allow user input etc... by default no
`nano file.txt`

Adding exec('stty cbreak'); to the PHP script also fixes the issue.
I put the following in a file defined by the auto_prepend_file setting in php.ini
So, I would do something like edit php.ini to the following:
auto_prepend_file = /path/to/prepend.php
Then in, /path/to/prepend.php, I would add the following line:
if (php_sapi_name() == 'cli') exec('stty cbreak');
I'm not exactly sure of the cause. I've read bug reports for PHP. I'm not sure about versions though. I noticed the problem with the following setup:
$ php -v
PHP 5.3.3 (cli) (built: Jul 12 2013 20:35:47)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
However, the following did not show the issue:
# php -v
PHP 5.3.26 (cli) (built: Oct 21 2013 16:50:03)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
with the ionCube PHP Loader v4.4.1, Copyright (c) 2002-2013, by ionCube Ltd.
It is worth noting that the version without the issue was using cPanel and the other was using the default CentOS 6 install via yum.

Related

Prevent comment appearing in output when including php code

I have the following at the beginning of my callable PHP scripts (Example: mytest.php)
#!/usr/bin/env php
<?php
This works exactly as hoped when called from the command line: the opening line is interpreted by the shell and PHP is called accordingly without any output to the terminal.
But if I do:
include('mytest.php') ;
in some other script, then that comment line gets output to the terminal (or, worse, to a web page). This looks like a bug to me since PHP should also interpret # as a comment, but in any case I would love to avoid the extra line of output. Looking for a way to make that comment line actually be ignored within include() ... or what suggestions to avoid the scenario?
> php --version
PHP 7.4.3-4ubuntu2.17 (cli) (built: Jan 10 2023 15:37:44) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.3-4ubuntu2.17, Copyright (c), by Zend Technologies
with Xdebug v3.0.4, Copyright (c) 2002-2021, by Derick Rethans
#!/usr/bin/env php
Is NOT a comment, it's a shebang telling the kernel how to interpret the contents of the file.
If you want to "catch" the output of a file you can use ob_start, ob_get_contents() and ob_end_clean like so:
<?php
ob_start(); // turn on output buffering
include "mytest.php";
$contents = ob_get_contents();
ob_end_clean(); // turn off output buffering
Then you can examine and filter the "comment" out of the output.
However, I'm not actually able to reproduce the issue on my computer (php v8), but that is how you can catch any output generated by the included PHP file.

rename() gives undocumented return value NULL

After an update from PHP7.0 to PHP7.2 some code stopped working and I don't understand why:
$res = rename($tmpfile, $output_file);
if ($res !== true) {
throw new Exception("Unable to rename temporary file");
}
In php7.2 the exception is triggered, although the rename was successfully done, the file is perfectly readable and nothing has gone wrong. Still I get the exception, because the return value is NULL. I inserted some debugging code:
$res = rename($tmpfile, $output_file);
log('Debug: $res ' . $res);
log('Debug: $res true? ' . $res === true);
log('Debug: $res type ' . gettype($res));
if ($res !== true) {
throw new Exception("Unable to rename temporary file");
}
This gives the following output:
2018-08-03 10:37:39 Debug: $res
2018-08-03 10:37:39
2018-08-03 10:37:39 Debug: $res type NULL
2018-08-03 10:37:39 CURL_DOWNLOAD Exception: Unable to rename temporary file
#0 /var/www/formr.org/application/Library/Functions.php(1470): CURL::DownloadUrl('http://docs.goo...', '/var/www/formr....', NULL, 'GET', Array, Array)
As you can see, there is no output, and the type is NULL. That is an undocumented behavior. According to the php documentation the return value must be nothing but true or false.
I couldn't find any documentation that the behavior of this function was changed. Does someone understand this?
Thanks for help!
Edit: var_dump output
$res = rename($tmpfile, $output_file);
ob_start();
var_dump($res);
$contents = ob_get_contents();
ob_end_clean();
log('Debug $res: ' . $contents);
Gives:
2018-08-03 12:37:40 Debug $res: NULL
Testing rename()-function in terminal:
user$ sudo su www-data -s /bin/bash
www-data$ php -a
Interactive mode enabled
php > $old='/var/www/path/changed/filename.xlsx';
php > $new='/var/www/path/changed/newfilename.xlsx';
php > $res = rename($old, $new);
php > var_dump($res);
bool(true)
(I changed the filenname and path for privacy reasons, but I tested it on the original file.)
PHP version:
$ php --version
PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul 4 2018 16:55:24) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies
The PHP version installed is the one that comes shipped with Ubuntu 18.04. I didn't recompile it myself.
The program has its own rename functions, at two times, as I just noticed. But they are not about renaming files, they rename something in the database.
They both have just one parameter. PHP does not support overloading or differentiation by number of arguments, so might there be some interference? So far this hasn't been an issue. And as the file is actually renamed, I don't believe that the wrong function is called.
Solved by last comment of Apokryfos: Properly calling the rename-function with namespace \rename($old,$new) gives me bool(true) as return value.

Real-time SSH output with Net_SSH (phpseclib)

I use Net_SSH (phpseclib) to execute SSH commands on an external server. I simply cannot figure out how to have real-time output from the command. I know how to make it run in the background so it's not dependant on the Apache process, but it's unclear how I'd go about showing the external output in real-time instead of having to wait for the command to finish.
My current code is as simple as $ssh->exec('command').
The PHP version used is:
[admin# ~]$ php -v
PHP 7.1.9 (cli) (built: Sep 10 2017 11:31:06) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
I managed to get it working with libssh2 and output buffering, see example below:
$session = ssh2_connect("server.local", 22, array('hostkey'=> 'ssh-rsa' )) or die("Couldn't connect to the SSH Server.");
ssh2_auth_pubkey_file($session, "root", "/path/to/public/key.pub", "/path/to/private/key") or die("couldn't authenticate to server"); // Authenticating to the server with a ssh-key for security purposes
while (ob_end_flush()); // end all output buffers if any
$proc = ssh2_exec($session, "ping -c 40 google.nl");
echo '<pre class="scroll">';
echo "[root#server ~]# ping -c 5 google.nl\n"; // Command you will execute
while (!feof($proc))
{
echo fread($proc, 4096); // Read the output from the command
# flush(); // Flushes the whole php buffer so we can output new data
}
echo "\nDone";
echo '</pre>';
Don't forget that you need php 5.6 or lower for ssh2, you can replace the command in the variable $proc by $ssh->exec('command') as you use it.
I was able to get it working using this:
$ssh->exec('ping 127.0.0.1', function($output) {
echo $output;
});
To eliminate the variability in how your system is configured versus mine I'll use Vagrant to establish a common configuration. To that end, here's my Vagrantfile:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
end
My full phpseclib code (using 1.0.7):
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('127.0.0.1', 2222);
$ssh->login('vagrant', 'vagrant');
$ssh->exec('ping 127.0.0.1', function($output) {
echo $output;
});
A youtube video of the output:
https://youtu.be/j9-q3024eEk
If it's not working then several possibilities exist.
Maybe the "command" you're running simply doesn't dump output real time. Or maybe it requires a PTY or something. It's hard to comment since you haven't said what the command you're trying to run is. As my post demonstrates there are commands that my solution does work with.
Maybe it works with Vagrant but not with your system. Maybe your system has been configured in some funky way or something. In this scenario I guess what'd help is if you provided the SSH logs. You can get them by doing define('NET_SSH2_LOGGING', 2); and then echo $ssh->getLog();. Post the results in pastebin.com and then post the link.
edit: if you're running this in a webserver vs in the CLI you may encounter issues with how the webserver is setup - issues that go past phpseclib. For example, does this output real time or does it lock up?:
while (true) {
echo "test\n";
sleep(1);
}
flush() / ob_flush() might help but ultimately this would depend on the webserver you're using (Apache, nginx, etc), the SAPI you're using (CGI, Apache module, etc), etc.
I would consider this to be a "funky configuration".

PHP hangs when call Python script that use multiprocess/multithreading

I've write a php code to call a Python script much like this:
<?php
system("tmn", $return_value);
echo $return_value;
?>
Below is the Python script.
#!/usr/bin/env python
import os
from subprocess import Popen
devnull = open(os.devnull, 'wb')
p = [] # ip -> process
for n in range(1, 20): # start ping processes
ip = "172.28.83.%d" % n
p.append((ip, Popen(['ping', '-c', '1', '-w', '1', ip], stdout=devnull)))
#NOTE: you could set stderr=subprocess.STDOUT to ignore stderr also
while p:
for i, (ip, proc) in enumerate(p[:]):
if proc.poll() is not None: # ping finished
p.remove((ip, proc)) # this makes it O(n**2)
if proc.returncode == 0:
print('%s active' % ip)
elif proc.returncode == 2:
print('%s no response' % ip)
else:
print('%s error' % ip)
devnull.close()
But when I load the php page using my broswer, the page will loading forever, it seems that PHP is stuck at the system or exec call.
I tried using different Python script, but as long as the script is parallel(using either Multiproccessing or Multithreading), this problem will definitely happen.
The weirdest thing is that this issue only happens on one of my linux server(CentOS 6.5).
$php -v
PHP 5.5.7 (cli) (built: Jan 3 2014 11:19:10)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2013 Zend Technologies
python --version
Python 2.7.6
I've squeeze my head all day for this. It would be a huge help if you give any suggestion.
This is probably widely off mark, but the rule of thumb solution for solving "weird problems which only happen on centos" is "have you tried disabling selinux?"
Maybe you should try disabling it (http://www.cyberciti.biz/faq/howto-turn-off-selinux/), rebooting and trying your code again. If it works, you will either learn to keep selinux disabled on all your systems or you will have an excellent adventure in trying to understand how selinux works, in which case, good luck and bring a lot of aspirin.

PHPUnit on pre-commit Git Hook seems to use different PHP version

I'm trying to set up a pre-commit git hook that will run and validate our unit tests. We are using PHPUnit in the Symfony 2 platform.
For some reason it seems that when I run the unit tests via the git hook that it is using a different version of PHP.
When I check my php version I get:
php -v
PHP 5.4.14 (cli) (built: May 8 2013 10:23:18)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
with Xdebug v2.2.1, Copyright (c) 2002-2012, by Derick Rethans
Here is my git hook:
#!/usr/bin/php
<?php
// Hook configuration
$project = 'My Project';
// Tell the commiter what the hook is doing
echo PHP_EOL;
echo '>> Starting unit tests'.PHP_EOL;
// Execute project unit tests
exec('bin/phpunit -c app/', $output, $returnCode);
// if the build failed, output a summary and fail
if ($returnCode !== 0)
{
// find the line with the summary; this might not be the last
while (($minimalTestSummary = array_pop($output)) !== null)
{
if (strpos($minimalTestSummary, 'Tests:') !== false)
{
break;
}
}
// output the status and abort the commit
echo '>> Test suite for '.$project.' failed:'.PHP_EOL;
echo $minimalTestSummary;
echo chr(27).'[0m'.PHP_EOL; // disable colors and add a line break
echo PHP_EOL;
exit(1);
}
echo '>> All tests for '.$project.' passed.'.PHP_EOL;
echo PHP_EOL;
exit(0);
When I run the unit tests manually ("bin/phpunit -c app/" from my project directory) the tests execute with out error. When I run the tests via the git hook I get a PHP Parse Error. I've determined that the parse error stems from the use of array bracket notation (['key'=>'value']) that was added in PHP 5.4
When I echo php -v in the git hook I get the following output
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
Since the Zend Engine is different (2.4.0 when run manually and 2.3.0 when run via the git hook) I'm assuming that there is a PHP version mismatch happening.
Does anyone have any clues as to why this is happening?
Thanks!
the git/phpunit executable most likely sees a different PATH environment variable than your shell (bash/zsh/...) executable.
If a program looks for i.e. php - the first match from your PATH variable will be used.
Probably you're adding a folder containing a different PHP executable to the PATH variable in one of your shell startup files.
Possible files include:
/etc/profile
/etc/zshenv
/etc/bashrc
~/.profile
~/.bashrc
~/.zshrc
...

Categories