views:

1350

answers:

9

I have a PHP script which executes a shell command:

$handle = popen('python last', 'r');
$read = fread($handle, 4096);
print_r($read);
pclose($handle);

I echo the output of the shell output. When I run this in the command I get something like this:

[root@localhost tester]# python last
[last] ZVZX-W3vo9I: Downloading video webpage
[last] ZVZX-W3vo9I: Extracting video information
[last] ZVZX-W3vo9I: URL: x
[download] Destination: here.flv
[download]   0.0% of 10.09M at     ---b/s ETA --:--
[download]   0.0% of 10.09M at   22.24k/s ETA 07:44
[download]   0.0% of 10.09M at   66.52k/s ETA 02:35
[download]   0.1% of 10.09M at  154.49k/s ETA 01:06
[download]   0.1% of 10.09M at  162.45k/s ETA 01:03

However, when I run that same command from PHP I get this output:

[last] ZVZX-W3vo9I: Downloading video webpage
[last] ZVZX-W3vo9I: Extracting video information
[last] ZVZX-W3vo9I: URL: x
[download] Destination: here.flv

As you can see the bottom bit is missing which is the bit I need!! The problem before was that the percentages were being updated on the same line but now I have changed my Python script so that it creates a new line. But this made difference! :(

This question is related to this one.

Thank you for any help.

Update

Needed to redirect output "2>&1". Arul got lucky :P since I missed the deadline to pick the one true answer which belonged to Pax!

+16  A: 

You read only the first 4,096 bytes from the pipe, you'll need to place the fread/print_r in a loop and check for the end-of-file using the feof function.

$handle = popen('python last', 'r');

while(!feof($handle))
{
    print_r(fread($handle, 4096));
} 

pclose($handle);
arul
But he is only getting about 155 bytes.
stesch
Thats a standard behavior when working with remote files in PHP.As mentioned in the documentation:"...reading will stop after a packet is available. This means that you should collect the data together in chunks as shown in the examples below."
arul
stesch's point may be that the answer begins "you read only the first 4,096 bytes from the pipe" which is a bit misleading.
Greg Ball
+2  A: 

Could you read in a while loop instead of using fread()?

while( !feof($handle) )
    echo fgets($handle);

You may have to flush() also.

ryanday
+2  A: 

What do you see with

python last | less

?

Maybe the output you want is emitted on STDERR. Then you have to start it this way:

$handle = popen('python last 2>&1', 'r');

The 2>&1 directs STDERR into STDOUT, which you are capturing with popen.

stesch
I see nothing wen i use the | less - wat does that mean?
Abs
+12  A: 

The first step is to see where the output is going. The first thing I would do is choose a slightly smaller file so that you're not waiting around for seven minutes for each test.

Step 1/ See where things are being written in the shell. Execute the command python last >/tmp/stdout 2>/tmp/stderr then look at those two files. Ideally, everything will be written to stdout but that may not be the case. This gives you the baseline behavior of the script.

Step 2/ Do the same thing when run from PHP by using $handle = popen('python last >/tmp/stdout 2>/tmp/stderr', 'r');. Your PHP script probably won't get anything returned in this case but the files should still be populated. This will catch any changed behavior when running in a non-terminal environment.

If some of the output goes to stderr, then the solution should be as simple as $handle = popen('python last 2>&1', 'r');

Additionally, the doco for PHP states (my bolding):

Returns a file pointer identical to that returned by fopen(), except that it is unidirectional (may only be used for reading or writing) and must be closed with pclose(). This pointer may be used with fgets(), fgetss(), and fwrite().

So I'm not sure you should even be using fread(), although it's shown in one of the examples. Still, I think line-based input maps more to what you're trying to achieve.

Irrespective of all this, you should read the output in a loop to ensure you can get the output when it's more than 4K, something like:

$handle = popen ('python last 2>&1', 'r');
if ($handle) {
    while(! feof ($handle)) {
        $read = fgets ($handle);
        echo $read;
    } 
    pclose ($handle);
}

Another thing to look out for, if you're output is going to a browser and it takes too long, the browser itself may time out since it thinks the server-side connection has disappeared. If you find a small file working and your 10M/1minute file not working, this may be the case. You can try flush() but not all browsers will honor this.

paxdiablo
This looks very promising. Will test it tomorrow night when I am back at my own machine.
Abs
Abs
+5  A: 

It is much much easier to do this:

$output = `python last`;
var_dump($output);

The 'ticks' (`) will execute the line and capture the output. Here is a test example:

File test.php:

<?php
echo "PHP Output Test 1\n";
echo "PHP Output Test 2\n";
echo "PHP Output Test 3\n";
echo "PHP Output Test 4\n";
echo "PHP Output Test 5\n";
?>

File capture.php:

<?php
$output = `php test.php`;
var_dump($output);
?>

Output from php capture.php:

string(80) "Test PHP Script
Test PHP Script
Test PHP Script
Test PHP Script
Test PHP Script
"

You can then split the output into an array based on line breaks:

$outputArray = explode('\n', $output);

OR use proc_open(), which gives you much more control than popen as you can specify where you want stdin, stdout and stderr to be handled.

OR you can fopen STDIN or php://stdin and then pipe to php:

? python last | php script.php

I would go with option 1 and using the backticks, easiest way.

nikcub
The problem with your approach is that the data isn't streamed. Rather, it will display all the information he wants after the script completes.
strager
You are right strager, I need to see the shell output in real time to gather progress.
Abs
+4  A: 

I would not be surprised to find that the progress report is omitted when the output is not going to a tty. That is, the PHP is capturing everything that is sent, but the progress report is not being sent.

There is ample precedent for commands behaving differently depending on where the output goes - starting with the good old ls command.

Of course, if you wrote the Python script that you're running, this is much less likely to be the cause of the trouble.

How can you verify whether this hypothesis is valid? Well, you could start by running the script at the command line with the standard output going to one file and the standard error going to another. If you see the progress information in one of those files, you know a whole lot more about what is going on. If you see the progress information on the screen still, then the script is probably echoing the progress information to /dev/tty, but when PHP runs it, there is no /dev/tty for the process. If you don't see the progress information at all (on screen or in a file), then my original hypothesis is probably verified.

Jonathan Leffler
wget does this. Depending on whether its a tty, a live-progress bar or after-download stats will be printed (both to stderr, iirc). Its unclear what "last" does, but the output looks similar to wget.
Richard Levasseur
+2  A: 

If you're just trying to show the progress of the python command inside a terminal window, then I would recommend the following instead:

<?php
system("python last > `tty`");

You won't need to capture the output then, and the user can even CTRL+C the download without aborting the PHP script.

too much php
I am trying show the output command in real time in the web browser.
Abs
I was trying to run the interactive mysql commandline shell. This did the trick! Thanks a lot.
Jonathon Hill
+3  A: 

Try running stream_get_contents() on $handle. It's a better way to work with resources where you don't know the exact size of what you're trying to retrieve.

Robert Elwell
+1  A: 

You are missing a flush call. (In your python app, and possibly your php app aswell)

That is because when you use standard stream stdin/stdout interactively (from cmdline) they are in a line-buffered mode (in short system flushes on each new line), but when you call it from within your program streams are in a fully buffered mode(doesn't output till system buffer is full).

More info on this here buffering in standard streams

Luka Marinko