shell_exec() in PHP - php

<?php
// Execute a shell script
$dump = shell_exec('bigfile.sh'); // This script takes some 10s to complete execution
print_r($dump); // Dump log to screen
?>
When the script above is executed from the browser, it loads for 10s and the dumps the output of the script to the screen. This is, of course, normal. But if I want the data written to STDOUT by the shell script to be displayed on the screen in real-time, is there some way I could do it?

I would add proc_open() which gives you much more control over command execution if you need it, if not try passthru() or popen() as it was mentioned before.

Try this:
$handle = proc_open('bigfile.sh', array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes);
$status = proc_close($handle);
It works great for me.

Try passthru() or popen()
The code will look something like this:
<?php
$fp=popen("bigfile.sh","r");
while (!feof($fp)) {
$results = fgets($fp, 256);
echo $result;
flush();
}
?>
As #wik suggest below you can also try proc_open instead of popen it should work in a similar fashion.

Related

Store php exec in a session variable

Is is possible to store an exec' output into a session variable while its running to see it's current progress?
example:
index.php
<?php exec ("very large command to execute", $arrat, $_SESSION['output']); ?>
follow.php
<php echo $_SESSION['output']); ?>
So, when i run index.php i could close the page and navigate to follow.php and follow the output of the command live everytime i refresh the page.
No, because exec waits for the spawned process to terminate before it returns. But it should be possible to do with proc_open because that function provides the outputs of the spawned process as streams and does not wait for it to terminate. So in broard terms you could do this:
Use proc_open to spawn a process and redirect its output to pipes.
Use stream_select inside some kind of loop to see if there is output to read; read it with the appropriate stream functions when there is.
Whenever output is read, call session_start, write it to a session variable and call session_write_close. This is the standard "session lock dance" that allows your script to update session data without holding a lock on them the whole time.
No, exec will run to completion and only then will store the result in session.
You should run a child process writing directly to a file and then read that file in your browser:
$path = tempnam(sys_get_temp_dir(), 'myscript');
$_SESSION['work_path'] = $path;
// Release session lock
session_write_close();
$process = proc_open(
'my shell command',
[
0 => ['pipe', 'r'],
1 => ['file', $path],
2 => ['pipe', 'w'],
],
$pipes
);
if (!is_resource($process)) {
throw new Exception('Failed to start');
}
fclose($pipes[0]);
fclose($pipes[2]);
$return_value = proc_close($process);
In your follow.php you can then just output the current output:
echo file_get_contents($_SESSION['work_path']);
No, You can't implement watching in this way.
I advise you to use file to populate status from index.php and read status from file in follow.php.
As alternative for file you can use Memcache

Run a .bat file in command prompt using PHP

I need to run a .bat file in a command prompt whenever I click a button or hyperlink. The code I've written is:
<?php
if(isset($_POST['submit']))
{
$param_val = 1;
$test='main.bat $par';
// exec('c:\WINDOWS\system32\cmd.exe /c START C:/wamp/www/demo/m.bat');
// exec('cmd /c C:/wamp/www/demo/m.bat');
// exec('C:/WINDOWS/system32/cmd.exe');
// exec('cmd.exe /c C:/wamp/www/demo/main.bat');
exec('$test');
}
else
{
?>
<form action="" method="post">
<input type="submit" name="submit" value="Run">
</form>
<?php
}
?>
my main.bat is:
#echo off
cls
:start
echo.
echo 1.append date and time into log file
echo 2.just ping google.com
set/p choice="select your option?"
if '%choice%'=='1' goto :choice1
if '%choice%'=='2' goto :choice2
echo "%choice%" is not a valid option. Please try again.
echo.
goto start
:choice1
call append.bat
goto end
:choice2
call try.bat
goto end
:end
pause
When I click the run button it has to open the command prompt and run the main.bat file, but whenever I click run it says nothing.
$test='main.bat $par';
exec('$test');
... won't work.
PHP only takes $variables in double quotation marks.
This is bad practice also: $test = "main.bat $par";.
Also windows takes backslashes instead of slashes which need to be escaped through another backslash in double quotes.
Use one of these:
$test = 'cmd /c C:\wamp\www\demo\main.bat ' . $par;
or
$test = "cmd /c C:\\wamp\\www\\demo\\main.bat {$par}";
run:
echo shell_exec($test);
Even more fails:
Remove the pause from the end of your script. PHP does not get arround that automatically.
Looking more at the batch file, I bet you don't even need it. Everything inside the batch file can be put into a PHP file.
As Elias Van Ootegem already mentioned, you would need to pipe in STDIN to enter your option (1, 2) into the batch file.
Since you run the PHP script through a browser, on a web server, the .bat file execution occurs on the web server not the client.
No matter if you run your server on the same computer, your bat may be executed but you can not interact with it.
The solution may be to make a bat that takes arguments instead of being interactive, and bring the interaction back to front the PHP script in order to call the bat execution with the correct args.
I‘ve tried this exec on my pc .
your bat would executed but you can't see the black interface. you could try the bat like #echo off
Echo tmptile > tmp.txt like this ,it could create the a file named tmp.txt which tells you. the bat was executed.
Assuming that you just want to simulate an interactive session, you just need to use proc_open() and related functions:
<?php
$command = escapeshellcmd('main.bat');
$input = '1';
$descriptors = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
);
$ps = proc_open($command, $descriptors, $pipes);
if(is_resource($ps)){
fwrite($pipes[0], $input);
fclose($pipes[0]);
while(!feof($pipes[1])){
echo fread($pipes[1], 4096);
}
fclose($pipes[1]);
$output = proc_close($ps);
if($output!=0){
trigger_error("Command returned $output", E_USER_ERROR);
}
}else{
trigger_error('Could not execute command', E_USER_ERROR);
}

Show output taken from shell_exec and display it in real time instead of after waiting 5-7min

Right now, I have code as follows.
$output = shell_exec( !-- unix commands are here --! );
echo $output;
I have a website where, upon the clicking of a particular button, the shell script is outputted and it is displayed on the browser. This is working perfectly. The only issue is that I can't see what's happening with the output until it is finished. I have to wait about 5-7 minutes, and then I see about a hundred lines of output. I am trying to push the output to the browser as the output executes -- I want to be able to see the output as its happening in real time (on the browser).
I've tried to use popen, proc_open, flush(), ob_start, etc. Nothing seems to be working. I just tried opening a text file, writing the contents of the output to the textfile, and reading the textfile incrementally on a loop. I'm a php beginner so it's possible that I haven't been using any of the above methods properly.
What is the simplest way to accomplish this?
Because PHP runs exec, system, pass_thru, etc in blocking mode, you are very limited in possibilities. PHP will require the code to finish executing before moving on throughout the script, unless you do something like add the following to your command:
"> /dev/null 2>/dev/null &"
Of course, this will halt the output of your command, but.. maybe something like:
exec('command > /cmd_file 2>/cmd_file &');
$file = fopen('/cmd_file', 'r');
while (!feof($file)) {
echo fgets($file);
sleep(1);
}
fclose($file);
Worth a shot.

PHP output to command line

I start my script from command line and it outputs things as they happen but a week ago it stopped outputing and now outputs everything when script finishes. I have ob_start() but as I know this does not effect command line output.
An easy way to do it is to create a function in php like this:
function console_log($message) {
$STDERR = fopen("php://stderr", "w");
fwrite($STDERR, "\n".$message."\n\n");
fclose($STDERR);
}
where $message is the desired output to command line. Then simply call the function wherever you would like to output and pass in whatever you want it to print.
You need to remove ob_start()... try this code on the command line, and it will print the text all at once:
<?
ob_start();
echo "test\n";
sleep(10);
echo "buffer\n";
?>
It'd be very helpful if you could post your script here, at least the relevant parts, but things I'd test are:
Did you turn on buffering?
Are you running the process in something like a nohup or something else that may be buffering it?
Did you change any other buffering settings?
Outputting only at the end of the script seems a buffering problem.

Run process with realtime output in PHP

I am trying to run a process on a web page that will return its output in realtime. For example if I run 'ping' process it should update my page every time it returns a new line (right now, when I use exec(command, output) I am forced to use -c option and wait until process finishes to see the output on my web page). Is it possible to do this in php?
I am also wondering what is a correct way to kill this kind of process when someone is leaving the page. In case of 'ping' process I am still able to see the process running in the system monitor (what makes sense).
This worked for me:
$cmd = "ping 127.0.0.1";
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
flush();
$process = proc_open($cmd, $descriptorspec, $pipes, realpath('./'), array());
echo "<pre>";
if (is_resource($process)) {
while ($s = fgets($pipes[1])) {
print $s;
flush();
}
}
echo "</pre>";
This is a nice way to show real time output of your shell commands:
<?php
header("Content-type: text/plain");
// tell php to automatically flush after every output
// including lines of output produced by shell commands
disable_ob();
$command = 'rsync -avz /your/directory1 /your/directory2';
system($command);
You will need this function to prevent output buffering:
function disable_ob() {
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
// Clear, and turn off output buffering
while (ob_get_level() > 0) {
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
}
// Disable apache output buffering/compression
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
}
It doesn't work on every server I have tried it on though, I wish I could offer advice on what to look for in your php configuration to determine whether or not you should pull your hair out trying to get this type of behavior to work on your server! Anyone else know?
Here's a dummy example in plain PHP:
<?php
header("Content-type: text/plain");
disable_ob();
for($i=0;$i<10;$i++)
{
echo $i . "\n";
usleep(300000);
}
I hope this helps others who have googled their way here.
Checked all answers, nothing works...
Found solution Here
It works on windows (i think this answer is helpful for users searching over there)
<?php
$a = popen('ping www.google.com', 'r');
while($b = fgets($a, 2048)) {
echo $b."<br>\n";
ob_flush();flush();
}
pclose($a);
?>
A better solution to this old problem using modern HTML5 Server Side Events is described here:
http://www.w3schools.com/html/html5_serversentevents.asp
Example:
http://sink.agiletoolkit.org/realtime/console
Code: https://github.com/atk4/sink/blob/master/admin/page/realtime/console.php#L40
(Implemented as a module in Agile Toolkit framework)
For command-line usage:
function execute($cmd) {
$proc = proc_open($cmd, [['pipe','r'],['pipe','w'],['pipe','w']], $pipes);
while(($line = fgets($pipes[1])) !== false) {
fwrite(STDOUT,$line);
}
while(($line = fgets($pipes[2])) !== false) {
fwrite(STDERR,$line);
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
return proc_close($proc);
}
If you're trying to run a file, you may need to give it execute permissions first:
chmod('/path/to/script',0755);
try this (tested on Windows machine + wamp server)
header('Content-Encoding: none;');
set_time_limit(0);
$handle = popen("<<< Your Shell Command >>>", "r");
if (ob_get_level() == 0)
ob_start();
while(!feof($handle)) {
$buffer = fgets($handle);
$buffer = trim(htmlspecialchars($buffer));
echo $buffer . "<br />";
echo str_pad('', 4096);
ob_flush();
flush();
sleep(1);
}
pclose($handle);
ob_end_flush();
I've tried various PHP execution commands on Windows and found that they differ quite a lot.
Don't work for streaming: shell_exec, exec, passthru
Kind of works: proc_open, popen -- "kind of" because you cannot pass arguments to your command (i.e. wont' work with my.exe --something, will work with _my_something.bat).
The best (easiest) approach is:
You must make sure your exe is flushing commands (see printf flushing problem). Without this you will most likely receive batches of about 4096 bytes of text whatever you do.
If you can, use header('Content-Type: text/event-stream'); (instead of header('Content-Type: text/plain; charset=...');). This will not work in all browsers/clients though! Streaming will work without this, but at least first lines will be buffered by the browser.
You also might want to disable cache header('Cache-Control: no-cache');.
Turn off output buffering (either in php.ini or with ini_set('output_buffering', 'off');). This might also have to be done in Apache/Nginx/whatever server you use in front.
Turn of compression (either in php.ini or with ini_set('zlib.output_compression', false);). This might also have to be done in Apache/Nginx/whatever server you use in front.
So in your C++ program you do something like (again, for other solutions see printf flushing problem):
Logger::log(...) {
printf (text);
fflush(stdout);
}
In PHP you do something like:
function setupStreaming() {
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Disable Apache output buffering/compression
if (function_exists('apache_setenv')) {
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
}
function runStreamingCommand($cmd){
echo "\nrunning $cmd\n";
system($cmd);
}
...
setupStreaming();
runStreamingCommand($cmd);
First check whether flush() works for you. If it does, good, if it doesn't it probably means the web server is buffering for some reason, for example mod_gzip is enabled.
For something like ping, the easiest technique is to loop within PHP, running "ping -c 1" multiple times, and calling flush() after each output. Assuming PHP is configured to abort when the HTTP connection is closed by the user (which is usually the default, or you can call ignore_user_abort(false) to make sure), then you don't need to worry about run-away ping processes either.
If it's really necessary that you only run the child process once and display its output continuously, that may be more difficult -- you'd probably have to run it in the background, redirect output to a stream, and then have PHP echo that stream back to the user, interspersed with regular flush() calls.
If you're looking to run system commands via PHP look into, the exec documentation.
I wouldn't recommend doing this on a high traffic site though, forking a process for each request is quite a hefty process. Some programs provide the option of writing their process id to a file such that you could check for, and terminate the process at will, but for commands like ping, I'm not sure that's possible, check the man pages.
You may be better served by simply opening a socket on the port you expect to be listening (IE: port 80 for HTTP) on the remote host, that way you know everything is going well in userland, as well as on the network.
If you're attempting to output binary data look into php's header function, and ensure you set the proper content-type, and content-disposition. Review the documentation, for more information on using/disabling the output buffer.
Try changing the php.ini file set "output_buffering = Off". You should be able to get the real time output on the page
Use system command instead of exec.. system command will flush the output
why not just pipe the output into a log file and then use that file to return content to the client. not quite real time but perhaps good enough?
I had the same problem only could do it using Symfony Process Components ( https://symfony.com/doc/current/components/process.html )
Quick example:
<?php
use Symfony\Component\Process\Process;
$process = new Process(['ls', '-lsa']);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
?>

Categories