PHP CLI: Как прочитать один символ ввода из TTY (не дожидаясь клавиши ввода)? - PullRequest
25 голосов
/ 10 сентября 2010

Я хочу читать по одному символу за раз из командной строки в 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 буферизует ввод.

В конечном итоге я хочу иметь возможность читать нажатия клавиш, такие как стрелка вверх, стрелка вниз и т. Д.

Ответы [ 4 ]

32 голосов
/ 10 сентября 2010

Решением для меня было установить режим -icanon на TTY (используя stty).Например:

1004

Итак, код, который сейчас работает:

#!/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");

?>
21 голосов
/ 07 февраля 2014

Вот способ, который работает для меня с функциями readline и stream, без необходимости связываться с 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;
  }
}

Протестировано с PHP 5.5.8 на OSX.

4 голосов
/ 05 июля 2016

Функция ниже представляет собой упрощенную версию ответа @ 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";
0 голосов
/ 01 июня 2017
<?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...";
}
...