“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 转换为字符串,并将分配一个 "" 终端。
似乎没有办法同时从 BOTH 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 "完成\n";
} else {
echo "Shell执行失败\n";
}
?>
根据需要调整超时时间。
我认为大多数人遇到的问题在于对阻塞的真正含义存在误解。
阻塞意味着从*流*读取将等待直到有数据可用。不一定是应用程序的所有数据——而是*一些*数据。因此,如果您执行的命令没有写入标准输出或写入大量数据,它将完全帮不上忙。
所以有两个问题
1. 如果您需要通过 `ssh2_exec` 知道静默程序是否完成,您需要自行发出信号。`ssh2_exec` *不会*阻塞执行,直到命令执行完毕。并且连续的两个 `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_地址';
$user = '用户名';
$pass = '密码';
$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]';你的命令在此处;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;
}
}
}
}
?>
[由danbrown AT php DOT net编辑:包含jschwepp AT gmail DOT com于2010年2月17日提供的错误修复,修复了函数名中的拼写错误。]
运行多个命令会显示此警告,并且第二个(以及任何后续)命令将不会执行
- PHP警告:ssh2_exec(): 无法从远程主机请求通道…
使用fclose($stream)的解决方案不起作用,但这是一个对我有效的简单解决方案
<?php
$connection = ssh2_connect('某个SSH IP地址', 22);
ssh2_auth_password($connection, '用户名', '密码');
//这些命令适用于某些华为企业路由器
$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。
当尝试使用此配置和将stream_set_blocking设置为true的ssh2_exec()时,脚本将无限期挂起。当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
// [打开连接]
// ...
$stream = ssh2_exec($connection, 'ps ax');
stream_set_blocking($stream, true);
// 如果没有读取到流的末尾,命令可能无法正确完成
$output = stream_get_contents($stream);
// ...
// [关闭流和连接]
?>