[英]How to get cursor position with PHP-CLI?
使用在CLI模式下運行的PHP腳本,我希望以可移植的方式獲取光標位置。
隨着代碼:
// Query Cursor Position
echo "\033[6n";
在終端中,此代碼報告光標位置,如
wb ?> ./script.php
^[[5;1R
wb ?> ;1R
但是,我無法在代碼中檢索這兩個值(行:5,列:1)。
在使用輸出緩沖進行一些測試之后:
ob_start();
echo "\033[6n";
$s = ob_get_contents();
file_put_contents('cpos.txt',$s);
我在cpos.txt文件中“\\ 033 [6n”,而不是設備答案。
並閱讀STDIN:
$timeout = 2;
$sent = false;
$t = microtime(true);
$buf = '';
stream_set_blocking(STDIN,false);
while(true){
$buf .= fread(STDIN,8);
if(!$sent){
echo "\033[6n";
$sent = true;
}
if($t+$timeout<microtime(true))
break;
}
var_dump($buf);
緩沖區為空,但終端顯示設備應答:
wb ?> ./script.php
^[[5;1R
string(0) ""
wb ?>
有沒有辦法,沒有詛咒,獲得光標位置?
你到目前為止所使用的代碼幾乎可以工作,你會發現按下enter並等待你的超時完成會產生一個包含答案的字符串,但最后會有一個\\n
字符。 (注意字符串長度為7而不是0。)
$ php foo.php
^[[2;1R
string(7) "
"
這里的問題是stream_set_blocking
不會阻止終端逐行緩沖輸入,因此終端在按下回車鍵之前不會向程序的stdin發送任何內容。
要使終端在沒有行緩沖的情況下立即向您的程序發送字符,您需要將終端設置為“非規范”模式。 這會禁用任何行編輯功能,例如按退格鍵刪除字符的功能,而是立即將字符發送到輸入緩沖區。 在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輸入時,或者在程序退出后在shell中輸入時,這可能會導致問題。 其次,來自終端^[[2;1R
的響應代碼仍然回顯到終端,這使得當你想做的所有事情都將你的程序輸出看成一個變量。
要解決輸入回顯問題,我們可以在stty
參數中添加-echo
以禁用終端中的輸入回顯。 要在我們更改之前將終端重置為其狀態,我們可以調用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,因為終端從未發送任何轉義序列,因此不會響應它。 解決方法是寫入/dev/tty
而不是stdout,如下所示:
$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兼容終端。 除非您在Cygwin / MSYS2下運行該程序,否則此代碼可能無法在Windows上運行。 我還建議您在此代碼中添加一些錯誤處理,以防您因任何原因未從終端獲得響應。
(這真是一個評論,但它有點長)
使用硬編碼終端序列距“便攜式”還有很長的路要走。 雖然目前大多數終端仿真器都支持ANSI,vt100或xterm代碼,這些代碼具有共同的基礎,但是有一個非常明確的API用於訪問稱為“curses”的交互式終端。 pecl中提供了PHP擴展 。 這只是curses系統的存根接口 - 存在於任何Unix / Linux系統上。 雖然可以在mswindows上使用cygwin或pdcurses進行設置,但這並不容易。 您沒有提到您正在使用的操作系統。 ( mswindows控制台使用ANSI序列)
有一個基於termcap(curses的前身)的工具包( hoa )可能很有用。
要“檢索”您只需要從stdin讀取的數據(盡管建議使用非阻塞)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.