【发布时间】:2021-04-04 05:40:14
【问题描述】:
问题描述
我想用 PHP 打开一个 Linux 伪终端,但似乎没有简单的方法可以做到这一点。我尝试了不同的解决方案,但似乎都不够好。
PTY 的目标是模拟具有与zsh 和sudo 等程序完美交互的能力的终端。包括 Python 和 C 在内的其他编程语言都有相应的函数或库。 Python 有PTY library 可以简单地做pty.spawn("/bin/zsh"),C 有openpty() 函数。
我理想的最终目标是拥有一个 PHP 函数,允许我在 PTY 终端中读写,并且不需要安装外部库。 (许多共享主机提供商不允许这样做。)
到目前为止我所尝试的
使用 proc_open()
我最初的想法是使用proc_open() PHP 函数创建一个带有stdin、stdout 和stderr 管道的Bash 终端(基于PHP documentation 中的示例#1)但是,很快就证明是有问题的,因为它实际上不是真正的 PTY。运行 stty -a 时出现 stty: stdin isn't a terminal 错误。以下是复制此内容的说明。
- 使用
php pty_test.php运行它 - 使用
cat /tmp/stdout读取shell 的输出。 - 用
> /tmp/stdin输入命令。
这是我用于此目的的 PHP 代码:
<?php
/* pty_test.php */
ini_set('display_errors', 1);
ini_set('display_startup_ūūerrors', 1);
error_reporting(E_ALL);
define("STD_IN", 0);
define("STD_OUT", 1);
define("STD_ERR", 2);
set_time_limit(0);
umask(0);
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = "/bin/sh -i ";
$stdin_fifo = "/tmp/stdin";
$stdout_fifo = "/tmp/stdout";
posix_mkfifo($stdin_fifo, 0644);
posix_mkfifo($stdout_fifo, 0644);
$resource_stdin = fopen($stdin_fifo, "rb+");
$resource_stdout = fopen($stdout_fifo, "wb+");
$descriptorspec = array(
STD_IN => array("pipe", "rb"),
STD_OUT => array("pipe", "wb"),
STD_ERR => array("pipe", "wb")
);
$process = proc_open($shell, $descriptorspec, $pipes, null, $env = null);
stream_set_blocking($pipes[STD_IN], 0);
stream_set_blocking($pipes[STD_OUT], 0);
stream_set_blocking($pipes[STD_ERR], 0);
stream_set_blocking($resource_stdin, 0);
stream_set_blocking($resource_stdout, 0);
while (1) {
$read_a = array($resource_stdin, $pipes[STD_OUT], $pipes[STD_ERR]);
$num_changed_streams = stream_select($read_a, $write_a, $error_a, null);
if (in_array($resource_stdin, $read_a)) {
$input = fread($resource_stdin, $chunk_size);
fwrite($pipes[STD_IN], $input);
}
if (in_array($pipes[STD_OUT], $read_a)) {
$input = fread($pipes[STD_OUT], $chunk_size);
fwrite($resource_stdout, $input);
}
if (in_array($pipes[STD_ERR], $read_a)) {
$input = fread($pipes[STD_ERR], $chunk_size);
fwrite($resource_stdout, $input);
}
}
fclose($resource_stdin);
fclose($resource_stdout);
fclose($pipes[STD_IN]);
fclose($pipes[STD_OUT]);
fclose($pipes[STD_ERR]);
proc_close($process);
unlink($stdin_fifo);
unlink($stdout_fifo);
?>
Python PTY
我注意到在非 pty shell(我在上面描述的)中运行 python3 -c "import pty;pty.spawn('/bin/bash');" 将产生一个完全交互式的 PTY shell,如我所愿。这导致了一个半好的解决方案:将 $shell 变量设置为 python3 -c "import pty;pty.spawn('/bin/bash')" 将使用 Python3 生成交互式 shell。但是依赖外部软件并不理想,因为并不总能保证拥有 Python3。 (而且这个解决方案也感觉太老套了......)
/dev/ptmx
我正在阅读proc_open()函数的source code,也找到了openpty()的来源。不幸的是,PHP 不能直接调用这个函数,但也许可以复制它的行为。
我可以fopen("/dev/ptmx","r+") 创建一个新的从站,但openpty() 也使用grantpt() 和unlockpt(),它们在PHP 中不可用。
外部函数接口
FFI 允许访问外部库。也许可以导入pty.h 并运行openpty()。不幸的是,FFI 是非常实验性的,可能并不总是可用。
TL;DR
使用 PHP 生成 PTY 的最安全、最可靠的方法是什么?
【问题讨论】:
-
expect_popen() 怎么样?我知道这取决于 PECL 扩展,但以防万一……
标签: php linux terminal tty pty