10

Setup and Background

I am working on script that needs to run as /usr/bin/php-cgi instead /usr/local/bin/php and I'm having trouble checking for stdin

If I use /usr/local/bin/php as the interpreter I can do something like

if defined('STDIN'){ ... }

This doesn't seem to work with php-cgi - Looks to always be undefined. I checked the man page for php-cgi but didn't find it very helpful. Also, if I understand it correctly, the STDIN constant is a file handle for php://stdin. I read somewhere that constant is not supposed to be available in php-cgi

Requirements

  • The shebang needs to be #!/usr/bin/php-cgi -q
  • The script will sometimes be passed arguments
  • The script will sometimes receive input via STDIN

Current Script

#!/usr/bin/php-cgi -q
<?php

$stdin = '';
$fh = fopen('php://stdin', 'r');

if($fh)
{

    while ($line = fgets( $fh )) {
            $stdin .= $line;
    }
    fclose($fh);

}

echo $stdin;

Problematic Behavior

This works OK:

$ echo hello | ./myscript.php 
hello

This just hangs:

./myscript.php 

These things don't work for me:

  • Checking defined('STDIN') // always returns false
  • Looking to see if CONTENT_LENGTH is defined
  • Checking variables and constants

I have added this to the script and run it both ways:

print_r(get_defined_constants());
print_r($GLOBALS);
print_r($_COOKIE);
print_r($_ENV);
print_r($_FILES);
print_r($_GET);
print_r($_POST);
print_r($_REQUEST);
print_r($_SERVER);
echo shell_exec('printenv');

I then diff'ed the output and it is the same.

I don't know any other way to check for / get stdin via php-cgi without locking up the script if it does not exist.

/usr/bin/php-cgi -v yields: PHP 5.4.17 (cgi-fcgi)

4 Answers 4

7

You can use the select function such as:

$stdin = '';
$fh = fopen('php://stdin', 'r');
$read  = array($fh);
$write = NULL;
$except = NULL;
if ( stream_select( $read, $write, $except, 0 ) === 1 ) {
    while ($line = fgets( $fh )) {
            $stdin .= $line;
    }
}
fclose($fh);
Sign up to request clarification or add additional context in comments.

1 Comment

perfect solution! covers all my requirements
3

Regarding your specific problem of hanging when there is no input: php stream reads are blocking operations by default. You can change that behavior with stream_set_blocking(). Like so:

$fh = fopen('php://stdin', 'r');
stream_set_blocking($fh, false);
$stdin = fgets($fh);
echo "stdin: '$stdin'";  // immediately returns "stdin: ''"

Note that this solution does not work with that magic file handle STDIN.

Comments

2

stream_get_meta_data helped me :)

And as mentioned in the previous answer by Seth Battin stream_set_blocking($fh, false); works very well 👍

The next code reads data from the command line if provided and skips when it's not.

For example: echo "x" | php render.php and php render.php

In the first case, I provide some data from another stream (I really need to see the changed files from git, something like git status | php render.php.

Here is an example of my solution which works:

$input = [];

$fp = fopen('php://stdin', 'r+');
$info = stream_get_meta_data($fp);

if (!$info['seekable'] && $fp) {
    while (false !== ($line = fgets($fp))) {
        $input[] = trim($line);
    }
    fclose($fp);
}

1 Comment

Note that this does not work with redirects. I.e. $ ./foo.php< data-file.txt
0

The problem is that you create a endless loop with the while($line = fgets($fh)) part in your code.

$stdin = '';
$fh = fopen('php://stdin','r');
if($fh) {
  // read *one* line from stdin upto "\r\n"
  $stdin = fgets($fh);
  fclose($fh);
}
echo $stdin;

The above would work if you're passing arguments like echo foo=bar | ./myscript.php and will read a single line when you call it like ./myscript.php If you like to read more lines and keep your original code you can send a quit signal CTRL + D

To get parameters passed like ./myscript.php foo=bar you could check the contents of the $argv variable, in which the first argument always is the name of the executing script:

 ./myscript.php foo=bar

 // File: myscript.php
 $stdin = '';
 for($i = 1; $i < count($argv); i++) {
    $stdin .= $argv[$i];
 }

I'm not sure that this solves anything but perhaps it give you some ideas.

3 Comments

thanks but i don't think that this is the problem - I tried putting an echo statement in my while loop before posting the question and it did not continuously spit out output - I really do believe it is "hanging" trying to read stdin
Strange if I echo in the while loop I see different content each time I press enter? So if you write something and press enter and then do a ctrl + d you don't get the output echoed at all?
@cwd - If you call the script without any input i.e ./myscript.php then it would stop at the fgets(...) until you press enter, isn't this the reason why you don't get any output if you add a echo inside your loop?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.