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

Posted

技术标签:

【中文标题】PHP CLI:如何从 TTY 读取单个字符的输入(无需等待回车键)?【英文标题】:PHP CLI: How to read a single character of input from the TTY (without waiting for the enter key)? 【发布时间】:2011-04-10 16:58:00 【问题描述】:

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

考虑这段代码:

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

?>

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

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 正在缓冲输入。

最终我希望能够阅读诸如向上箭头、向下箭头等按键。

【问题讨论】:

【参考方案1】:

我的解决方案是在 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# 

对此处给出的答案的支持:Is there a way to wait for and get a key press from a (remote) terminal session?

欲了解更多信息,请参阅: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");

?>

【讨论】:

"完成后不要忘记重置 TTY!" -- 我们如何重置它? Mark:最基本的你可以做到stty sane。但是,为了保证您将 tty 重置为之前的确切状态,您应该首先保存它的状态。我已经扩展了答案以包括如何做到这一点的示例。【参考方案2】:

这是一种适用于我的 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 测试。

【讨论】:

readline 需要编译成 PHP。使用以下命令检查 --with-readline=/opt/local 的配置:php -i | grep readline 使用(最近更新的)Arch Linux 和 PHP 7.0.5。 break 导致它读取一个字符然后停止;删除它使一切正常。我还将select 末尾的0 更改为NULL,现在PHP 不使用100% CPU(!)。 根据初始测试,此功能在我的机器上似乎可以完美运行(捕获 \033s 和所有内容)。 @i336_ 感谢您指出这一点,我编辑了答案并将 stream_select 的 tv_sec 设置为 null 以解决 CPU 使用问题。但是,我无法重现任何停顿。 啊,太好了!关于停顿,我发现这个问题可以在 ideone 上重现! ideone.com/EkJwIC(取消/评论 break 以进行测试)。我怀疑这是因为 OS X 的默认 tty 设置与 Linux 不同。 请注意,使用此方法无法区分元键等。实际上,任何与输出字符不对应的键(箭头键、F1、F2 等)都会在 $c 中返回 x1B 或 ESC,但至少在我的终端模拟器中,例如 CTRL+W 确实返回“^W”或 0x13我【参考方案3】:

下面的函数是@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";

【讨论】:

【参考方案4】:
<?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...";

【讨论】:

它似乎不起作用。根据stream_set_blocking手册说This function works for any stream that supports non-blocking mode (currently, regular files and socket streams).恐怕STDIN不属于那里。 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请edit您的回答添加一些解释,包括您所做的假设。 ref

以上是关于PHP CLI:如何从 TTY 读取单个字符的输入(无需等待回车键)?的主要内容,如果未能解决你的问题,请参考以下文章

Javascript没有从输入字段中读取单个字符

C ++从文件中读取多行到单个字符串

禁止从 /dev/tty 读取

禁止从/ dev / tty读取

如何在单个输入中(单独)读取由空格分隔的字符串? [关闭]

5.2与终端进行对话