简体   繁体   English

如何使用PHP-CLI获取光标位置?

[英]How to get cursor position with PHP-CLI?

With a PHP script which runs in CLI mode, I want to get the cursor position in a portable way. 使用在CLI模式下运行的PHP脚本,我希望以可移植的方式获取光标位置。

With the code : 随着代码:

// Query Cursor Position
echo "\033[6n";

In the terminal, this code reports the cursor position, as 在终端中,此代码报告光标位置,如

wb ?> ./script.php 
^[[5;1R
wb ?> ;1R 

But, I can't retrieve the two values (row: 5, column: 1) in the code. 但是,我无法在代码中检索这两个值(行:5,列:1)。

After some tests with output buffering : 在使用输出缓冲进行一些测试之后:

ob_start();
echo "\033[6n";
$s = ob_get_contents();
file_put_contents('cpos.txt',$s);

I've "\\033[6n" in the cpos.txt file, not the device answer. 我在cpos.txt文件中“\\ 033 [6n”,而不是设备答案。

And reading STDIN : 并阅读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);

The buffer is empty but the terminal show the device answer : 缓冲区为空,但终端显示设备应答:

wb ?> ./script.php 
^[[5;1R
string(0) ""
wb ?>

Is there a way, without curses, to get the cursor position ? 有没有办法,没有诅咒,获得光标位置?

The code you have so far almost works, and you'll find that hitting enter and waiting for your timeout to complete does produce a string containing the answer, but with a \\n character on the end. 你到目前为止所使用的代码几乎可以工作,你会发现按下enter并等待你的超时完成会产生一个包含答案的字符串,但最后会有一个\\n字符。 (Note the string length of 7 instead of 0.) (注意字符串长度为7而不是0。)

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

The issue here is that stream_set_blocking does not prevent the terminal from buffering input line-by-line, so the terminal doesn't send anything to stdin of your program until the enter key is pressed. 这里的问题是stream_set_blocking不会阻止终端逐行缓冲输入,因此终端在按下回车键之前不会向程序的stdin发送任何内容。

To make the terminal send characters immediately to your program without line-buffering, you need to set the terminal to "non-canonical" mode. 要使终端在没有行缓冲的情况下立即向您的程序发送字符,您需要将终端设置为“非规范”模式。 This disables any line-editing features, such as the ability to press backspace to erase characters, and instead sends characters to the input buffer immediately. 这会禁用任何行编辑功能,例如按退格键删除字符的功能,而是立即将字符发送到输入缓冲区。 The easiest way to do this in PHP is to call the Unix utility stty . 在PHP中执行此操作的最简单方法是调用Unix实用程序stty

<?php
system('stty -icanon');

echo "\033[6n";
$buf = fread(STDIN, 16);

var_dump($buf);

This code successfully captures the response from the terminal into $buf . 此代码成功捕获终端对$buf的响应。

$ php foo.php
^[[2;1Rstring(6) ""

However, this code has a couple of issues. 但是,此代码有几个问题。 First of all, it doesn't re-enable canonical mode in the terminal after it's finished. 首先,它在终端完成后不会重新启用终端中的规范模式。 This could cause issues when trying to input from stdin later in your program, or in your shell after your program exits. 当您尝试在程序中稍后从stdin输入时,或者在程序退出后在shell中输入时,这可能会导致问题。 Secondly, the response code from the terminal ^[[2;1R is still echoed to the terminal, which makes your program's output look messy when all you want to do is read this into a variable. 其次,来自终端^[[2;1R的响应代码仍然回显到终端,这使得当你想做的所有事情都将你的程序输出看成一个变量。

To solve the input echoing issue, we can add -echo to the stty arguments to disable input echoing in the terminal. 要解决输入回显问题,我们可以在stty参数中添加-echo以禁用终端中的输入回显。 To reset the terminal to its state before we changed it, we can call stty -g to output a list of current terminal settings which can be passed to stty later to reset the terminal. 要在我们更改之前将终端重置为其状态,我们可以调用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);

Now when running the program, we don't see any junk displayed in the terminal: 现在运行程序时,我们看不到终端中显示的任何垃圾:

$ php foo.php 
string(6) ""

One last potential improvement we can make to this is to allow the program to be run when stdout is redirected to another process / file. 我们可以做的最后一个改进是允许在stdout重定向到另一个进程/文件时运行程序。 This may or may not be necessary for your application, but currently, running php foo.php > /tmp/outfile will not work, as echo "\\033[6n"; 这对你的应用程序来说可能是必需的,但是目前,运行php foo.php > /tmp/outfile将不起作用,如echo "\\033[6n"; will write straight to the output file rather than to the terminal, leaving your program waiting for characters to be sent to stdin as the terminal was never sent any escape sequence so will not respond to it. 将直接写入输出文件而不是终端,让程序等待字符发送到stdin,因为终端从未发送任何转义序列,因此不会响应它。 A workaround for this is to write to /dev/tty instead of stdout as follows: 解决方法是写入/dev/tty而不是stdout,如下所示:

$term = fopen('/dev/tty', 'w');
fwrite($term, "\033[6n");
fclose($term); // Flush and close the file.

Putting this all together, and using bin2hex() rather than var_dump() to get a listing of characters in $buf , we get the following: 把这一切放在一起,并使用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";

We can see that the program works correctly as follows: 我们可以看到该程序正常工作如下:

$ php foo.php > /tmp/outfile
$ cat /tmp/outfile
1b5b323b3152
$ xxd -p -r /tmp/outfile | xxd
00000000: 1b5b 323b 3152                           .[2;1R

This shows that $buf contained ^[[2;1R , indicating the cursor was at row 2 and column 1 when its position was queried. 这表明$buf包含^[[2;1R ,表示光标在其位置被查询时位于第2行和第1列。

So now all that's left to do is to parse this string in PHP and extract the row and column separated by the semicolon. 所以现在剩下要做的就是在PHP中解析这个字符串并提取由分号分隔的行和列。 This can be done with a regex. 这可以使用正则表达式完成。

<?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";

This gives the following output: 这给出了以下输出:

Row: 123, Col: 456

It's worth noting that all this code is only portable to Unix-like operating systems and ANSI/VT100-compatible terminals. 值得注意的是,所有这些代码只能移植到类Unix操作系统和ANSI / VT100兼容终端。 This code may not work on Windows unless you run the program under Cygwin / MSYS2. 除非您在Cygwin / MSYS2下运行该程序,否则此代码可能无法在Windows上运行。 I'd also recommend that you add some error handling to this code in case you don't get the response from the terminal that you expect for whatever reason. 我还建议您在此代码中添加一些错误处理,以防您因任何原因未从终端获得响应。

(this is really a comment, but it's a bit long) (这真是一个评论,但它有点长)

Using hard coded terminal sequences is a very long way from "portable". 使用硬编码终端序列距“便携式”还有很长的路要走。 While most terminal emulators available currently will support ANSI, vt100 or xterm codes which have a common base there is a very well defined API for accessing interactive terminals known as "curses". 虽然目前大多数终端仿真器都支持ANSI,vt100或xterm代码,这些代码具有共同的基础,但是有一个非常明确的API用于访问称为“curses”的交互式终端。 A PHP extension is available in pecl . pecl中提供了PHP扩展 This is just a stub interface to the curses system - present on any Unix/Linux system. 这只是curses系统的存根接口 - 存在于任何Unix / Linux系统上。 While it is possible to set this up on mswindows, using cygwin or pdcurses, it's not an easy fit. 虽然可以在mswindows上使用cygwin或pdcurses进行设置,但这并不容易。 You omitted to mention what OS you are working on. 您没有提到您正在使用的操作系统。 (The mswindows console uses ANSI sequences) mswindows控制台使用ANSI序列)

There is a toolkit ( hoa ) based on termcap (predecessor to curses) which might be useful. 有一个基于termcap(curses的前身)的工具包( hoa )可能很有用。

To "retrieve" the data you just need to read from stdin (although it would be advisable to uses non-blocking up for this). 要“检索”您只需要从stdin读取的数据(尽管建议使用非阻塞)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM