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(!)。 根据初始测试,此功能在我的机器上似乎可以完美运行(捕获 \033
s 和所有内容)。
@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 读取单个字符的输入(无需等待回车键)?的主要内容,如果未能解决你的问题,请参考以下文章