【问题标题】:How to get cursor position with PHP-CLI?如何使用 PHP-CLI 获取光标位置?
【发布时间】:2019-09-17 10:22:09
【问题描述】:

使用在 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”,而不是设备答案。

阅读标准输入:

$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 ?>

有没有一种方法,没有诅咒,获得光标位置?

【问题讨论】:

  • 没有光标。
  • 你的意思是在PHP脚本中无法获取光标位置?

标签: php terminal command-line-interface


【解决方案1】:

到目前为止,您的代码几乎可以正常工作,您会发现按 Enter 并等待超时完成确实会生成一个包含答案的字符串,但末尾带有一个 \n 字符。 (注意字符串长度是 7 而不是 0。)

$ php foo.php
^[[2;1R                           
string(7) "
"

这里的问题是stream_set_blocking 不会阻止终端逐行缓冲输入,因此在按下回车键之前,终端不会向程序的标准输入发送任何内容。

要使终端在不使用行缓冲的情况下立即向您的程序发送字符,您需要将终端设置为"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) ""

但是,此代码有几个问题。首先,它完成后不会在终端中重新启用规范模式。当您稍后在程序中尝试从标准输入输入时,这可能会导致问题,或者在您的程序退出后在您的 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 &gt; /tmp/outfile 将不起作用,因为echo "\033[6n"; 将直接写入输出文件而不是终端,让您的程序等待字符发送到标准输入由于终端从未发送任何转义序列,因此不会响应它。解决方法是写入/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 兼容终端。除非您在 Cygwin / MSYS2 下运行该程序,否则此代码可能无法在 Windows 上运行。我还建议您在此代码中添加一些错误处理,以防无论出于何种原因您都没有从终端获得预期的响应。

【讨论】:

    【解决方案2】:

    (这确实是评论,但有点长)

    使用硬编码的终端序列距离“便携”还有很长的路要走。虽然目前可用的大多数终端仿真器将支持具有共同基础的 ANSI、vt100 或 xterm 代码,但有一个非常明确的 API 用于访问称为“curses”的交互式终端。一个PHP extension is available in pecl。这只是 curses 系统的一个存根接口——存在于任何 Unix/Linux 系统上。虽然可以在 mswindows 上使用 cygwin 或 pdcurses 进行设置,但这并不容易。您省略了提及您正在使用的操作系统。 (mswindows console uses ANSI 序列)

    有一个基于 termcap(curses 的前身)的工具包 (hoa) 可能有用。

    要“检索”您只需要从标准输入读取的数据(尽管为此建议使用非阻塞)。

    【讨论】:

    • 在一个完美的世界里,我希望代码可以在任何可以解析和执行一些 PHP 代码的平台上运行。现在,我在 MacOSX 上测试它。
    • 如果你忽略 mswindows (brew install ncurses),让东西“跨平台”很容易
    • 我完全同意应该使用 ncurses 库,但是从 2020 年 2 月开始,pecl 扩展将无法在 PHP 7 或更高版本中编译。我找不到任何暗示它会在未来随时更新的信息。
    猜你喜欢
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 2011-02-04
    • 1970-01-01
    相关资源
    最近更新 更多