簡體   English   中英

如何使用PHP-CLI獲取光標位置?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM