Код, который у вас есть, почти работает, и вы обнаружите, что нажатие клавиши ввода и ожидание завершения тайм-аута приводит к строке, содержащей ответ, но с символом \n
в конце.(Обратите внимание, что длина строки 7 вместо 0.)
$ php foo.php
^[[2;1R
string(7) "
"
Проблема здесь в том, что stream_set_blocking
не препятствует тому, чтобы терминал буферизовал вход за строкой, поэтому терминал не отправляетвсе, что находится в вашей программе, до тех пор, пока не будет нажата клавиша ввода.
Чтобы терминал немедленно отправлял символы в вашу программу без буферизации строки, вам необходимо установить для терминала "неканонический" *Режим 1009 *.Это отключает любые функции редактирования строк, такие как возможность нажимать клавишу Backspace, чтобы стереть символы, и вместо этого немедленно отправляет символы во входной буфер.Самый простой способ сделать это в PHP - вызвать утилиту Unix stty
.
<?php
system('stty -icanon');
echo "\033[6n";
$buf = fread(STDIN, 16);
var_dump($buf);
. Этот код успешно фиксирует ответ от терминала в $buf
.
$ php foo.php
^[[2;1Rstring(6) ""
.Тем не менее, этот код имеет несколько проблем.Прежде всего, он не повторно включает канонический режим в терминале после его завершения.Это может вызвать проблемы при попытке ввода из stdin позже в вашей программе или в вашей оболочке после выхода из программы.Во-вторых, код ответа от терминала ^[[2;1R
все еще отражается на терминале, что делает вывод вашей программы грязным, когда все, что вы хотите сделать, это прочитать это в переменную.
Чтобы решить проблему с отражением вводамы можем добавить -echo
к аргументам stty
, чтобы отключить эхо-ввод ввода в терминалеЧтобы вернуть терминал в его состояние до того, как мы изменили его, мы можем вызвать stty -g
, чтобы вывести список текущих настроек терминала, которые можно передать на stty
позже для сброса терминала.
<?php
// Save terminal settings.
$ttyprops = trim(`stty -g`);
// Disable canonical input and disable echo.
system('stty -icanon -echo');
echo "\033[6n";
$buf = fread(STDIN, 16);
// Restore terminal settings.
system("stty '$ttyprops'");
var_dump($buf);
Сейчаспри запуске программы мы не видим никакой ненужной информации в терминале:
$ php foo.php
string(6) ""
Последнее возможное улучшение, которое мы можем внести в это, - это позволить программе запускаться, когда stdout перенаправляется в другой процесс./ файл.Это может или не может быть необходимо для вашего приложения, но в настоящее время запуск php foo.php > /tmp/outfile
не будет работать, так как echo "\033[6n";
будет записывать данные напрямую в выходной файл, а не в терминал, в результате чего ваша программа будет ожидать отправки символов в stdin.поскольку терминалу никогда не отправлялась какая-либо escape-последовательность, он не будет отвечать на него.Обходной путь для этого - написать в /dev/tty
вместо стандартного вывода следующим образом:
$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term); // Flush and close the file.
Соберите все это вместе и используйте bin2hex()
вместо var_dump()
, чтобы получить список символов в $buf
мы получаем следующее:
<?php
$ttyprops = trim(`stty -g`);
system('stty -icanon -echo');
$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term);
$buf = fread(STDIN, 16);
system("stty '$ttyprops'");
echo bin2hex($buf) . "\n";
Мы видим, что программа работает правильно следующим образом:
$ php foo.php > /tmp/outfile
$ cat /tmp/outfile
1b5b323b3152
$ xxd -p -r /tmp/outfile | xxd
00000000: 1b5b 323b 3152 .[2;1R
Это показывает, что $buf
содержит ^[[2;1R
, указывая курсорнаходился в строке 2 и столбце 1, когда запрашивалась его позиция.
Так что теперь осталось только проанализировать эту строку в PHP и извлечь строку и столбец, разделенные точкой с запятой.Это можно сделать с помощью регулярного выражения.
<?php
// Example response string.
$buf = "\033[123;456R";
$matches = [];
preg_match('/^\033\[(\d+);(\d+)R$/', $buf, $matches);
$row = intval($matches[1]);
$col = intval($matches[2]);
echo "Row: $row, Col: $col\n";
Это дает следующий вывод:
Row: 123, Col: 456
Стоит отметить, что весь этот код переносим только в Unix-подобные операционные системы и ANSI/ VT100-совместимые терминалы.Этот код может не работать в Windows, если вы не запустите программу под Cygwin / MSYS2.Я также рекомендую вам добавить к этому коду некоторую обработку ошибок на случай, если вы не получите ожидаемый от терминала ответ по какой-либо причине.