我想从 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 正在缓冲输入。
最终我希望能够读取按键,例如向上箭头、向下箭头等。
我的解决方案是在 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");
?>
这是一种适合我的 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 进行测试。
下面的函数是@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";
<?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...";
}
以下函数将等待用户输入字符,然后立即返回。这种方法支持多字节字符,因此也适用于检测箭头键按下。
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;
}
如果您不在 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);
}
fgets()
来读取每个循环中的所有行,而不是一个接一个地读取字符。因此,如果用户在第二秒之间输入 abracadabra,则循环不会迭代每个字符。