“pty” 参数没有记录。
如果您想模拟 pty,您应该传递 pty 模拟名称(“vt102”、“ansi”等...),或者如果您不希望,则传递 NULL。
传递 false 将把 false 转换为字符串,并将分配一个 "" 终端。
(PECL ssh2 >= 0.9.0)
ssh2_exec — 在远程服务器上执行命令
$session
,$command
,$pty
= ?,$env
= ?,$width
= 80,$height
= 25,$width_height_type
= SSH2_TERM_UNIT_CHARS在远程端执行命令并为其分配一个通道。
session
一个 SSH 连接链接标识符,从调用 ssh2_connect() 获取。
command
pty
env
env
可以作为名称/值对的关联数组传递,以在目标环境中设置。
width
虚拟终端的宽度。
height
虚拟终端的高度。
width_height_type
width_height_type
应该为 SSH2_TERM_UNIT_CHARS
或 SSH2_TERM_UNIT_PIXELS
之一。
成功时返回一个流,失败时返回 false
。
示例 #1 执行命令
<?php
$connection = ssh2_connect('shell.example.com', 22);
ssh2_auth_password($connection, 'username', 'password');
$stream = ssh2_exec($connection, '/usr/local/bin/php -i');
?>
“pty” 参数没有记录。
如果您想模拟 pty,您应该传递 pty 模拟名称(“vt102”、“ansi”等...),或者如果您不希望,则传递 NULL。
传递 false 将把 false 转换为字符串,并将分配一个 "" 终端。
似乎没有办法同时从 STDERR 和 STDOUT 流中获取数据,或者至少我还没有找到一种方法。
以下代码 *应该* 获取写入 STDERR 的错误消息,但是如果首先为 STDOUT 运行 stream_get_contents() 的调用,则随后对 STDERR 的调用将不会返回任何内容。
如果语句顺序相反,则对 STDERR 的调用将返回任何错误,而对 STDOUT 的调用将不返回任何内容
<?php
// 运行一个可能写入 STDERR 的命令(除非您有一个名为 /hom 的文件夹)
$stream = ssh2_exec($connection, "cd /hom");
$errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
// 为两个流启用阻塞
stream_set_blocking($errorStream, true);
stream_set_blocking($stream, true);
// 下面的两个命令中,哪个命令先列出,就会接收其相应的输出。第二个命令接收不到任何内容
echo "Output: " . stream_get_contents($stream);
echo "Error: " . stream_get_contents($errorStream);
// 关闭流
fclose($errorStream);
fclose($stream);
?>
我最初的怀疑是,要么 a) 我做错了什么,要么 b) 两个流的阻塞性质意味着,当第一个流接收数据并返回时,第二个流的缓冲区已经清空了。
我已经对禁用阻塞进行了初步测试,但也没有成功。
以下函数可用于读取 STDOUT 和 STDERR,即使这两个流都包含千兆字节的数据。它解决了其他评论中提到的大多数问题,例如阻塞,例如进一步使用现有连接等等。
function ssh2_run($ssh2,$cmd,&$out=null,&$err=null){
$result=false;
$out='';
$err='';
$sshout=ssh2_exec($ssh2,$cmd);
if($sshout){
$ssherr=ssh2_fetch_stream($sshout,SSH2_STREAM_STDERR);
if($ssherr){
# 我们不能将 stream_select() 与 SSH2 流一起使用
# 所以使用非阻塞的 stream_get_contents() 和 usleep()
if(stream_set_blocking($sshout,false) and
stream_set_blocking($ssherr,false)
){
$result=true;
# 循环直到 STDOUT 和 STDERR 上的输出结束
$wait=0;
while(!feof($sshout) or !feof($ssherr)){
# 仅在没有读取任何数据后睡眠
if($wait)usleep($wait);
$wait=50000; # 1/20 秒
if(!feof($sshout)){
$one=stream_get_contents($sshout);
if($one===false){ $result=false; break; }
if($one!=''){ $out.=$one; $wait=0; }
}
if(!feof($ssherr)){
$one=stream_get_contents($ssherr);
if($one===false){ $result=false; break; }
if($one!=''){ $err.=$one; $wait=0; }
}
}
}
# 我们需要等待命令结束
stream_set_blocking($sshout,true);
stream_set_blocking($ssherr,true);
# 这些将不会获得任何输出
stream_get_contents($sshout);
stream_get_contents($ssherr);
fclose($ssherr);
}
fclose($sshout);
}
return $result;
}
这是我用于对长时间运行的脚本执行 ssh2_exec 的方法,扩展了其他示例。它等待超时并检查两个流是否已完成。不使用阻塞,它从 STDOUT 和 STDERR 中获取数据。
<?php
$stdout = ssh2_exec($ssh, $cmd);
$stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR);
if (!empty($stdout)) {
$t0 = time();
$err_buf = null;
$out_buf = null;
// 尝试 30 秒
do {
$err_buf.= fread($stderr, 4096);
$out_buf.= fread($stdout, 4096);
$done = 0;
if (feof($stderr)) {
$done++;
}
if (feof($stdout)) {
$done++;
}
$t1 = time();
$span = $t1 - $t0;
// 信息提示
echo "while (($tD < 20) && ($done < 2));\n";
// 这里等待,避免循环过于频繁
sleep(1);
} while (($span < 30) && ($done < 2));
echo "STDERR:\n$err_buf\n";
echo "STDOUT:\n$out_buf\n";
echo "Done\n";
} else {
echo "无法连接 Shell\n";
}
?>
根据需要调整超时时间。
我认为很多人在这里遇到的问题是,他们对阻塞的真正含义存在误解。
阻塞意味着从 *流* 中读取将等待直到有数据。不一定是从应用程序获取所有数据 - 而是 *一些* 数据。因此,如果您执行的是不写入 stdout 或写入大量数据的命令,它对您毫无帮助。
所以有 2 个问题
1. 如果您需要通过 ssh2_exec 了解静默程序是否已完成,您需要向自己发送信号。ssh2_exec *不会* 阻塞执行,直到命令执行完成。并且 2 个连续的 ssh2_exec 可能异步执行。您也可以通过 ssh2_shell 登录到 shell 并解析到下一个提示符 - 但这太过分了。您还可以通过在命令末尾添加某种哨兵来实现这一点,例如 echo "@,",然后阻塞读取,直到您看到 "@." 确保 "@" 不会出现在输出中,或者如果无法做到这一点,请通过某种编码机制对输出进行转义。
2. 如果程序需要一段时间,您会遇到相同的问题。您需要读取,直到完成。因此,您需要一个类似上述的哨兵值。
3. 在这里使用 sleep() 只是一个坏主意。命令很少需要相同的时间来执行两次命令。如果您只做 *一件事* 并且可以等待 5 秒,那么这可能没问题。但如果这是您在循环中做的事情,那就很不好了。
希望这有帮助!
执行远程 Windows 命令...
经过一番折腾,我想建议一些可能对其他人有帮助的事情
1. 使用 'ssh2_fetch_stream' 根据手册打开一个 stderr 流。
2. Windows shell 命令需要 'cmd /C [command]' 来执行。(我忘了这一点)
<?php
// 代码片段
$stdout_stream = ssh2_exec($connection, 'cmd /C dir');
$stderr_stream = ssh2_fetch_stream($stdout_stream, SSH2_STREAM_STDERR);
?>
直到我看到 stderr 响应 'dir: not found',我才意识到需要 'cmd /C'。
如果您使用的是 exec 函数,并且在输出 > 1000 行时遇到问题
您应该使用
<?php
stream_set_blocking($stream, true);
while($line = fgets($stream)) {
flush();
echo $line."<br />";
}
?>
除了
<?php
stream_set_blocking($stream, true);
echo stream_get_contents($stream);
?>
从 SSH2 扩展的 0.13 版本开始,可以使用 stream_get_meta_data() 获取命令退出状态。
例如
<?php
$channel = ssh2_exec($connection, $command);
$metadata = stream_get_meta_data($channel);
$exitCode = $metadata['exit_status'];
?>
获取退出代码非常有用,因为通常情况下,命令在成功时返回 0,在出错时返回其他值。
如果您想在同一个进程中使用相同的变量 $stream 例如通过 ssh2 运行多个 exec 命令,并且要读取每个命令的输出,您必须在每个命令之后关闭流,否则您将无法读取下一个命令的输出。
<?php
$stream=ssh2_exec($conn_id,"/usr/bin/ls .");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);
if(ereg("public_html",$cmd)){
// ...........................................
}
$stream=ssh2_exec($conn_id,"/usr/bin/ls public_html");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);
if(ereg("images",$cmd)){
// ..........................................
}
$stream=ssh2_exec($conn_id,"/usr/bin/ls public_html/images");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);
if(ereg("blabla",$cmd)){
// ..........................................
}
?>
这是我发现的自动运行多个命令或可能需要比预期时间更长的命令的最佳方法。注意:这假设输出中没有出现 "[end]" 文本,否则函数将提前结束。希望这能帮助到大家。
<?php
$ip = 'ip_address';
$user = 'username';
$pass = 'password';
$connection = ssh2_connect($ip);
ssh2_auth_password($connection,$user,$pass);
$shell = ssh2_shell($connection,"bash");
//技巧在于开始和结束的echo,可以在*nix和windows系统中执行。
//如果在windows系统上,请在$cmd的开头添加'cmd /C'。
$cmd = "echo '[start]';your commands here;echo '[end]'";
$output = user_exec($shell,$cmd);
fclose($shell);
function user_exec($shell,$cmd) {
fwrite($shell,$cmd . "\n");
$output = "";
$start = false;
$start_time = time();
$max_time = 2; //时间以秒为单位
while(((time()-$start_time) < $max_time)) {
$line = fgets($shell);
if(!strstr($line,$cmd)) {
if(preg_match('/\[start\]/',$line)) {
$start = true;
}elseif(preg_match('/\[end\]/',$line)) {
return $output;
}elseif($start){
$output[] = $line;
}
}
}
}
?>
[由 php DOT net 的 danbrown 编辑:包含 (jschwepp AT gmail DOT com) 在 2010 年 2 月 17 日提供的错误修复,修复了函数名称中的拼写错误。]
运行多个命令会显示此警告,第二个(以及任何后续)命令将不会执行
- PHP 警告:ssh2_exec():无法从远程主机请求通道,在...
使用 fclose($stream) 的解决方案不起作用,但这对我来说是一个简单的解决方案
<?php
$connection = ssh2_connect('SOME SSH IP ADDRESS', 22);
ssh2_auth_password($connection, 'username', 'passwortd');
//这些命令是针对某些华为企业路由器
$stream = ssh2_exec( $connection, "screen-length 0 temporary\ndisplay isis name-table" );
stream_set_blocking( $stream, true );
$stream_out = ssh2_fetch_stream( $stream, SSH2_STREAM_STDIO );
echo stream_get_contents($stream_out);
?>
第一个命令是:screen-length 0 temporary
第二个命令是:display isis name-table
它在同一行中使用 \n 执行。
您必须稍后分离这些命令的输出。
看起来 ssh2_exec 在您连接到的会话中分配了 exec 流,为了释放它,您必须执行 ssh2_disconnect。
通常,您可以执行一个 ssh2_exec 命令,然后断开连接,然后连接并执行下一个 ssh2_exec 命令。这是我所见过的其他一些 php 库的工作方式。但有时某些变量依赖于会话,当您断开连接时,变量会重置。
如果 ssh2_exec 运行需要一段时间,并且您需要握手,可以使用文件。在这种情况下,$flag 命名一个握手文件,该文件在 ssh 脚本完成时写入。
<?php
$stream = ssh2_exec($connection, $command . " $public_key $private_key;");
$i=0;
while (!file_exists ($flag) && ($i < MAX_TIME))
{
sleep(1);
$i++;
}
$ret_val=($stream!=FALSE);
?>
这是正在运行的 bash 脚本的摘录。确保允许 Web 服务器读取脚本写入的文件。
echo 0 > $OUTFILE
chmod 666 $OUTFILE
另一个说明:您可以通过分号(;)将多个命令放在一起。
重要的是要注意,这并不适用于所有允许 SFTP 的主机。我们拒绝 shell 访问,但允许使用 sftp-internal 子系统进行 SFTP。
当尝试使用此配置使用 ssh2_exec(),并将 stream_set_blocking 设置为 true 时,脚本将无限期挂起。当 stream_set_blocking 设置为 false 时,将不会返回任何内容。
即使您可以 SFTP 文件,也要确保与您的主机确认您是否有 shell 访问权限。
*** 重要 ***
如果您在 PHP 5.2.X 上获取 STDERR 时遇到问题,请确保更新到 5.2.17 和来自 PECL 的最新 ssh2 扩展。
奇怪的是,我注意到如果我尝试执行任何操作,都不会发生任何事情。现在,我的意思是“没有返回内容!”..我的意思是,“我告诉它执行的命令没有执行!”。
除非遵循以下模式
<?php
$stream = ssh2_exec($connection, "touch ~/some_fake_file", 'xterm');
stream_get_contents($stream);
?>
手册中没有任何地方暗示 stream_get_contents 是实际执行所必需的,但在这种情况下.. 似乎是这样。因此,如果您在执行命令时遇到问题,请尝试使用 stream_get_contents()。
<?php
$connection = ssh2_connect('shell.example.com', 22);
ssh2_auth_password($connection, 'username', 'password');
$stream = ssh2_exec($connection, '/usr/local/bin/php -i');
?>
如果流未设置为阻塞模式,则命令可能在函数返回之前未完成。您可能必须将流设置为阻塞模式才能从命令中获取任何输出。
<?php
// [打开连接]
// ...
$stream = ssh2_exec($connection, 'ps ax');
stream_set_blocking($stream, true);
// 如果不将流读到末尾,命令可能无法正常完成
$output = stream_get_contents($stream);
// ...
// [关闭流和连接]
?>