通过在 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
不会阻止终端逐行缓冲输入,因此在按下 Enter 键之前终端不会向程序的标准输入发送任何内容。
要使终端立即向您的程序发送字符而不需要行缓冲,您需要将终端设置为 "non-canonical" 模式。这会禁用任何行编辑功能,例如按退格键删除字符的功能,而是立即将字符发送到输入缓冲区。在 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
的响应代码仍然回显到终端,这使得当您只想将其读入变量时,程序的输出看起来很困惑。
要解决输入回显问题,我们可以将 -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";
> 将直接写入输出文件而不是终端,让您的程序等待字符发送到标准输入,因为终端从未发送任何转义序列,因此不会响应它。解决此问题的方法是写入 /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 上运行。我还建议您向此代码添加一些错误处理,以防您因某种原因没有从终端收到预期的响应。
关于php - 如何使用 PHP-CLI 获取光标位置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55892416/