21

Is there anyway to read from STDIN with PHP that is non blocking:

I tried this:

stream_set_blocking(STDIN, false);
echo fread(STDIN, 1);

and this:

$stdin = fopen('php://stdin', 'r');
stream_set_blocking($stdin, false);
echo 'Press enter to force run command...' . PHP_EOL;
echo fread($stdin, 1);

but it still blocks until fread gets some data.

I noticed a few open bug reports about this (7 years old), so if it can't be done, does any one know any crude hacks that could accomplish this (on Windows and Linux)?

14
  • 3
    Not sure I understand what the non-blocking behaviour should be. If there's nothing waiting to be fed to stdin, should fread() return a FALSE? How would it differentiate that from EOF? It seems to me you need some sort of test other than fread() to determine whether there's waiting data on stdin, since there isn't a descriptive failure. Commented Feb 20, 2012 at 5:09
  • @Graham fread will not return, i.e. pause the execution of the script, until input is given to STDIN. Basicly what I want, is to check if there is any user input to STDIN, and if not, continue, or else run some other stuff. Commented Feb 20, 2012 at 20:20
  • @Graham "Non blocking" means, that fread() should immediately return, even if it couldn't read the amount of data, that it should read (the second argument), but less then this. In this case it should return an empty string. @Petah did you try fopen('php://stdin)? I vaguely remember, that I had issues with STDIN earlier. Commented Feb 20, 2012 at 20:25
  • @KingCrunch, just tried that, (as per my updated question), but had the same result. Commented Feb 20, 2012 at 20:33
  • So... the behaviour that's supposed to happen with stream_set_blocking(STDIN, false) is that fread() never waits, and if (strlen(fread($stdin,1)) == 0), then nothing is waiting to be input? Commented Feb 20, 2012 at 22:16

6 Answers 6

16
+50

Here's what I could come up with. It works fine in Linux, but on Windows, as soon as I hit a key, the input is buffered until enter is pressed. I don't know a way to disable buffering on a stream.

<?php

function non_block_read($fd, &$data) {
    $read = array($fd);
    $write = array();
    $except = array();
    $result = stream_select($read, $write, $except, 0);
    if($result === false) throw new Exception('stream_select failed');
    if($result === 0) return false;
    $data = stream_get_line($fd, 1);
    return true;
}

while(1) {
    $x = "";
    if(non_block_read(STDIN, $x)) {
        echo "Input: " . $x . "\n";
        // handle your input here
    } else {
        echo ".";
        // perform your processing here
    }
}

?>
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks, this will work for my case. But if you can get it consistent with (the enter key thing) then even better. Ill award you the bounty if there are no better answers.
I integrated this with my script, it works pretty well, except for the fact that it has to wait for enter. Every time I alt+tab to the Window, it pauses and waits for enter.
Just stubmle'd upon this question here and it might work if you're using stream_set_read_buffer(STDIN, 0); and, as you already mentioned, stream_set_blocking(STDIN, false); - The first one should set stdin to be unbuffered on reading, the second one set's it to be non blocking which is a bit different from unbuffered.
This is not working for me... It keeps me asking for an input to continue.
on windows it's not perfect, see:stackoverflow.com/questions/21464457/…
|
5

Just a notice, that non blocking STDIN working, now.

1 Comment

stream_set_blocking(STDIN, 0); does indeed work
2
system('stty cbreak');
while(true){
    if($char = fread(STDIN, 1)) {
        echo chr(8) . mb_strtoupper($char);
    }
}

2 Comments

i don't know why this worked, but it absolutely worked for me! (ubuntu linux 18.04 gnu bash 4.4) thanks
This solution needs to be combined with Martin's solution. Otherwise, it won't have a "processing loop" while "waiting for a char". I mean: This works immediately, but only responds to a keypress. Martin's allows processing while no key is pressed. Good point for the stty trick.
1

Petah, I can't help with the PHP side of this directly, but I can refer you to an article I ran across a while ago in which someone emulated transistors by testing within a shell script for the existence of pending data for a named pipe. It's a fascinating read, and takes shell scripting to a whole new level of geekiness. :-)

The article is here: http://www.linusakesson.net/programming/pipelogic/

So ... in answer to your "crude hacks" request, I suppose you could shunt your stdio through named pipes, then exec() the tool whose source is included at the URL above to test whether anything is waiting to be sent through the pipe. You'd probably want to develop some wrapper functions to help with stuff.

I suspect the pipelogic solution is Linux-only, or at least would require a unix-like operating system. No idea how this could be accomplished on Windows.

2 Comments

Sorry, I don't really see how this helps with my problem.
@Petah - a non-blocking fread() that reads from a pipe could be duplicated by a function that would only run a blocking fread() after checking whether there's any data waiting on a pipe. The tool at the link I posted lets you test that condition. I'm pretty sure that this approach would not work in Windows because of the lack of named pipes, so I won't work it further, since Windows compatibility is a condition of your question.
1

When building CLI utilities with PHP I wanted my utility to both utilize arguments and/or pipes.
To solve this issue I've came up with this snippet that can be put at the top of your script.

$stdin = null;
$is_stdin = fstat(STDIN)["size"] > 0;

if ($is_stdin) {
    while (true) {
        $c = fgetc(STDIN);

        // Why? Read this: https://www.php.net/manual/en/function.feof.php#67261
        if ($c === false)
            break;

        $stdin .= $c;
    }
}

This way you'll be able to check whether STDIN or Arguments were provided to the script.
You can utilize both at the same time if you want.

Comments

0

Using Martins Example above, and adding user3684669 code with a little modification of my own. Here is a small program displaying a clock and updating every second. When ever a key is pressed I echo it to the screen. I also hide the default echoing of the character to the screen. Works in Linux.

<?php
//-- Save the keyboard state
//-- then use cbreak and turn off echo
$stty_orig = '';
function init_keyboard() {
  global $stty_orig;

  $stty_orig = shell_exec("stty -g");
  system('stty cbreak -echo');
}

//-- Return keyboard state
function close_keyboard() {
  global $stty_orig;

  system('stty '.$stty_orig);
}

//-- clear the screen
function clear() {
  echo "\x1B[2J\x1B[0m";
}

//-- move cursor to x,y on the screen
function gotoxy($x, $y) {
  echo "\x1B[".$y.";".$x."H";
}

//-- print a string at x,y on the screen
function printxy($x, $y, $str) {
  gotoxy($x,$y);
  echo $str;
}

//-- Check if we have a character and return it if we do.
function non_block_read($fd) {
    $read = array($fd);
    $write = array();
    $except = array();
    $result = stream_select($read, $write, $except, 0);
    if($result === false) throw new Exception('stream_select failed');
    if($result === 0) return false;
    $data = stream_get_line($fd, 1);
    return $data;
}

//-- main program starts here.
init_keyboard();
clear();
printxy(20,15,'Press Q to quit!');
while(1) {
    $x = "";
    if($x = non_block_read(STDIN)) {
        //echo "Input: " . $x . "\n";
        // handle your input here
        printxy(20,13,'Last Key Pressed: ['.$x."]  ");
        if ($x == 'Q') {
            printxy(1,20,"bye!\n");
            break;
        }
    } else {
        // perform your processing here
        $date_time = date('F j,Y h:i:sa');
        printxy(20,12,$date_time."   ");
    }
    sleep(1);
}
close_keyboard();

Comments

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.