PHP CLI:如何从 TTY 读取输入的单个字符(无需等待回车键)?

发布于 2024-09-18 17:54:22 字数 721 浏览 4 评论 0原文

我想从 PHP 的命令行一次读取一个字符,但是似乎有某种输入缓冲从某个地方阻止了这一点。

考虑这段代码:

#!/usr/bin/php
<?php
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

输入“foo”作为输入(并按 Enter 键),我得到的输出是:

input# foo
Read from STDIN: f
input# Read from STDIN: o
input# Read from STDIN: o
input# Read from STDIN: 

input# 

期望的输出是:(

input# f
input# Read from STDIN: f

input# o
input# Read from STDIN: o

input# o
input# Read from STDIN: o

input# 
input# Read from STDIN: 

input# 

也就是说,字符被读取和处理为它们是打字的)。

然而,目前,每个字符只有在按下回车键后才会被读取。我怀疑 TTY 正在缓冲输入。

最终我希望能够读取按键,例如向上箭头、向下箭头等。

I want to read a single character at-a-time from the command line in PHP, however it seems as though there is some kind of input buffering from somewhere preventing this.

Consider this code:

#!/usr/bin/php
<?php
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

Typing in "foo" as the input (and pressing enter), the output I am getting is:

input# foo
Read from STDIN: f
input# Read from STDIN: o
input# Read from STDIN: o
input# Read from STDIN: 

input# 

The output I am expecting is:

input# f
input# Read from STDIN: f

input# o
input# Read from STDIN: o

input# o
input# Read from STDIN: o

input# 
input# Read from STDIN: 

input# 

(That is, with characters being read and processed as they are typed).

However, currently, each character is being read only after enter is pressed. I have a suspicion the TTY is buffering the input.

Ultimately I want to be able to read keypresses such as UP arrow, DOWN arrow, etc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

赏烟花じ飞满天 2024-09-25 17:54:22

我的解决方案是在 TTY 上设置 -icanon 模式(使用 stty)。例如:

stty -icanon

所以,现在有效的代码是:

#!/usr/bin/php
<?php
system("stty -icanon");
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

输出:

input# fRead from STDIN: f
input# oRead from STDIN: o
input# oRead from STDIN: o
input# 
Read from STDIN: 

input# 

此处给出的答案的道具:
有没有办法从(远程)终端会话等待并获取按键?

有关详细信息,请参阅:
http://www.faqs.org/docs/Linux -HOWTO/Serial-Programming-HOWTO.html#AEN92

完成后不要忘记恢复 TTY...

恢复 tty 配置

将终端重置回可以通过在更改 tty 状态之前保存它来完成此操作。完成后您可以恢复到该状态。

例如:

<?php

// Save existing tty configuration
$term = `stty -g`;

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to the original configuration
system("stty '" . $term . "'");

?>

这是保留 tty 并将其恢复为用户在开始之前所拥有的状态的唯一方法。

请注意,如果您不担心保留原始状态,则只需执行以下操作即可将其重置回默认的“正常”配置:

<?php

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to sane defaults
system("stty sane");

?>

The solution for me was to set -icanon mode on the TTY (using stty). Eg.:

stty -icanon

So, the the code that now works is:

#!/usr/bin/php
<?php
system("stty -icanon");
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

Output:

input# fRead from STDIN: f
input# oRead from STDIN: o
input# oRead from STDIN: o
input# 
Read from STDIN: 

input# 

Props to the answer given here:
Is there a way to wait for and get a key press from a (remote) terminal session?

For more information, see:
http://www.faqs.org/docs/Linux-HOWTO/Serial-Programming-HOWTO.html#AEN92

Don't forget to restore the TTY when you're done with it...

Restoring the tty configuration

Resetting the terminal back to the way it was can be done by saving the tty state before you make changes to it. You can then restore to that state when you're done.

For example:

<?php

// Save existing tty configuration
$term = `stty -g`;

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to the original configuration
system("stty '" . $term . "'");

?>

This is the only way to preserve the tty and put it back how the user had it before you began.

Note that if you're not worried about preserving the original state, you can reset it back to a default "sane" configuration simply by doing:

<?php

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to sane defaults
system("stty sane");

?>
迎风吟唱 2024-09-25 17:54:22

这是一种适用于我的 readline 和流函数的方法,而不需要弄乱 tty 的东西。

readline_callback_handler_install('', function() { });
while (true) {
  $r = array(STDIN);
  $w = NULL;
  $e = NULL;
  $n = stream_select($r, $w, $e, null);
  if ($n && in_array(STDIN, $r)) {
    $c = stream_get_contents(STDIN, 1);
    echo "Char read: $c\n";
    break;
  }
}

在 OSX 上使用 PHP 5.5.8 进行了测试。

Here is a way that works for me with readline and stream functions, without needing to mess with tty stuff.

readline_callback_handler_install('', function() { });
while (true) {
  $r = array(STDIN);
  $w = NULL;
  $e = NULL;
  $n = stream_select($r, $w, $e, null);
  if ($n && in_array(STDIN, $r)) {
    $c = stream_get_contents(STDIN, 1);
    echo "Char read: $c\n";
    break;
  }
}

Tested with PHP 5.5.8 on OSX.

清晨说晚安 2024-09-25 17:54:22

下面的函数是 @seb 答案的简化版本,可用于捕获单个字符。它不需要 stream_select,并使用 readline_callback_handler_install 固有的阻塞,而不是创建 while 循环。它还删除处理程序以允许正常进一步输入(例如 readline)。

function readchar($prompt)
{
    readline_callback_handler_install($prompt, function() {});
    $char = stream_get_contents(STDIN, 1);
    readline_callback_handler_remove();
    return $char;
}

// example:
if (!in_array(
    readchar('Continue? [Y/n] '), ["\n", 'y', 'Y']
    // enter/return key ("\n") for default 'Y'
)) die("Good Bye\n");
$name = readline("Name: ");
echo "Hello {$name}.\n";

The function below is a simplified version of @seb's answer that can be used to capture a single character. It does not require stream_select, and uses readline_callback_handler_install's inherent blocking rather than creating a while loop. It also removes the handler to allow further input as normal (such as readline).

function readchar($prompt)
{
    readline_callback_handler_install($prompt, function() {});
    $char = stream_get_contents(STDIN, 1);
    readline_callback_handler_remove();
    return $char;
}

// example:
if (!in_array(
    readchar('Continue? [Y/n] '), ["\n", 'y', 'Y']
    // enter/return key ("\n") for default 'Y'
)) die("Good Bye\n");
$name = readline("Name: ");
echo "Hello {$name}.\n";
还不是爱你 2024-09-25 17:54:22
<?php
`stty -icanon`;
// this will do it
stream_set_blocking(STDIN, 0);
echo "Press 'Q' to quit\n";
while(1){
   if (ord(fgetc(STDIN)) == 113) {
       echo "QUIT detected...";
       break;
   }
   echo "we are waiting for something...";
}
<?php
`stty -icanon`;
// this will do it
stream_set_blocking(STDIN, 0);
echo "Press 'Q' to quit\n";
while(1){
   if (ord(fgetc(STDIN)) == 113) {
       echo "QUIT detected...";
       break;
   }
   echo "we are waiting for something...";
}
寂寞陪衬 2024-09-25 17:54:22

以下函数将等待用户输入字符,然后立即返回。此方法支持多字节字符,因此也适用于检测箭头键按下。

function waitForInput(){

    $input = '';

    $read = [STDIN];
    $write = null;
    $except = null;

    readline_callback_handler_install('', function() {});

    // Read characters from the command line one at a time until there aren't any more to read
    do{
        $input .= fgetc(STDIN);
    } while(stream_select($read, $write, $except, 0, 1));

    readline_callback_handler_remove();

    return $input;

}

下面是使用上述函数来识别箭头键按下的示例:

$input = waitForInput();

switch($input){
    case chr(27).chr(91).chr(65):
        print 'Up Arrow';
        break;
    case chr(27).chr(91).chr(66):
        print 'Down Arrow';
        break;
    case chr(27).chr(91).chr(68):
        print 'Left Arrow';
        break;
    case chr(27).chr(91).chr(67):
        print 'Right Arrow';
        break;
    default:
        print 'Char: '.$input;
        break;
}

The following function will wait until the user enters a character and then returns it immediately. This approach supports multibyte characters so will also work for detecting arrow key presses.

function waitForInput(){

    $input = '';

    $read = [STDIN];
    $write = null;
    $except = null;

    readline_callback_handler_install('', function() {});

    // Read characters from the command line one at a time until there aren't any more to read
    do{
        $input .= fgetc(STDIN);
    } while(stream_select($read, $write, $except, 0, 1));

    readline_callback_handler_remove();

    return $input;

}

Here is an example of using the above function to identify an arrow key press:

$input = waitForInput();

switch($input){
    case chr(27).chr(91).chr(65):
        print 'Up Arrow';
        break;
    case chr(27).chr(91).chr(66):
        print 'Down Arrow';
        break;
    case chr(27).chr(91).chr(68):
        print 'Left Arrow';
        break;
    case chr(27).chr(91).chr(67):
        print 'Right Arrow';
        break;
    default:
        print 'Char: '.$input;
        break;
}
べ繥欢鉨o。 2024-09-25 17:54:22

如果您不在 Windows powershell/cmd 中,请使用 stream_set_blocking(),正如 joeldg 已经说过的那样。我正在使用 WSL (Ubuntu) 并且它有效:

stream_set_blocking(STDIN, false); 
echo "write 'q' to quit\n"; 
while (1) {
    $in = fgets(STDIN);
    if (!empty($in)) {
        if ($in === 'q') {
            echo "exit\n";
            break;
        } else
            echo "write 'q' to quit\n";
    }
    echo "do my job 1 sec\n"; sleep(1);
}

在我的代码中我使用 < code>fgets() 在每个循环中读取所有行,而不是一个接一个地读取字符。因此,如果用户在第二秒之间输入 abracadabra,则循环不会迭代每个字符。

If you're not in Windows powershell/cmd, use stream_set_blocking(), as joeldg already said. I'm use WSL (Ubuntu) and it's worked:

stream_set_blocking(STDIN, false); 
echo "write 'q' to quit\n"; 
while (1) {
    $in = fgets(STDIN);
    if (!empty($in)) {
        if ($in === 'q') {
            echo "exit\n";
            break;
        } else
            echo "write 'q' to quit\n";
    }
    echo "do my job 1 sec\n"; sleep(1);
}

In my code I use fgets() to read all line in every loop rather than characters one by one. So if user type abracadabra between second, then loop wouldn't iterate every character.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文