本文目录导读:

- 方案一:使用 phpseclib 库(纯PHP实现,无需SSH2扩展)
- 方案二:使用 SSH2 扩展(性能更好)
- 方案三:并行执行(提高效率)
- 方案四:使用 Symfony Process 组件(推荐生产环境)
- 结果汇总与展示
- 推荐使用场景
- 建议
在PHP项目中批量执行SSH命令并汇总结果,常见的有以下几种方案:
使用 phpseclib 库(纯PHP实现,无需SSH2扩展)
安装
composer require phpseclib/phpseclib:~3.0
批量执行示例
<?php
require_once 'vendor/autoload.php';
use phpseclib3\Net\SSH2;
class SSHCommandExecutor
{
private $results = [];
public function executeBatch($servers, $commands)
{
foreach ($servers as $index => $server) {
$ssh = new SSH2($server['host'], $server['port'] ?? 22);
if (!$ssh->login($server['username'], $server['password'])) {
$this->results[] = [
'server' => $server['host'],
'status' => 'failure',
'error' => 'Login failed'
];
continue;
}
$serverResults = [];
foreach ($commands as $cmd) {
$output = $ssh->exec($cmd);
$serverResults[] = [
'command' => $cmd,
'output' => $output,
'exit_code' => $ssh->getExitStatus()
];
}
$this->results[] = [
'server' => $server['host'],
'status' => 'success',
'commands' => $serverResults
];
$ssh->disconnect();
}
return $this->results;
}
public function summarizeResults()
{
$summary = [
'total_servers' => count($this->results),
'successful' => 0,
'failed' => 0,
'server_details' => []
];
foreach ($this->results as $result) {
if ($result['status'] === 'success') {
$summary['successful']++;
} else {
$summary['failed']++;
}
$summary['server_details'][] = $result;
}
return $summary;
}
}
// 使用示例
$executor = new SSHCommandExecutor();
$servers = [
['host' => '192.168.1.100', 'username' => 'root', 'password' => 'pass1'],
['host' => '192.168.1.101', 'username' => 'root', 'password' => 'pass2']
];
$commands = [
'uptime',
'free -h',
'df -h'
];
$results = $executor->executeBatch($servers, $commands);
$summary = $executor->summarizeResults();
// 输出结果
echo json_encode($summary, JSON_PRETTY_PRINT);
使用 SSH2 扩展(性能更好)
<?php
class SSH2BatchExecutor
{
private $connections = [];
private $results = [];
public function connectBatch($servers)
{
foreach ($servers as $key => $server) {
$connection = ssh2_connect($server['host'], $server['port'] ?? 22);
if (!$connection) {
$this->results[$key] = [
'status' => 'failure',
'error' => 'Connection failed'
];
continue;
}
if (!ssh2_auth_password($connection, $server['username'], $server['password'])) {
$this->results[$key] = [
'status' => 'failure',
'error' => 'Authentication failed'
];
continue;
}
$this->connections[$key] = $connection;
}
}
public function executeCommands($commands)
{
foreach ($this->connections as $key => $connection) {
$commandResults = [];
foreach ($commands as $cmd) {
$stream = ssh2_exec($connection, $cmd);
stream_set_blocking($stream, true);
$stdout = stream_get_contents($stream);
fclose($stream);
// 获取stderr
$stderr = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
$errorOutput = stream_get_contents($stderr);
fclose($stderr);
$commandResults[] = [
'command' => $cmd,
'stdout' => $stdout,
'stderr' => $errorOutput
];
}
$this->results[$key] = [
'server' => $key,
'status' => 'success',
'commands' => $commandResults
];
}
return $this->results;
}
public function generateReport($format = 'table')
{
$report = [];
foreach ($this->results as $server => $result) {
if ($result['status'] === 'success') {
foreach ($result['commands'] as $cmdResult) {
$report[] = [
'server' => $server,
'command' => $cmdResult['command'],
'status' => 'success',
'output' => $cmdResult['stdout']
];
}
} else {
$report[] = [
'server' => $server,
'command' => 'N/A',
'status' => 'failed',
'output' => $result['error']
];
}
}
return $report;
}
public function disconnectAll()
{
foreach ($this->connections as $connection) {
ssh2_disconnect($connection);
}
}
}
并行执行(提高效率)
<?php
class ParallelSSHExecutor
{
private $results = [];
public function executeParallel($servers, $commands, $callback = null)
{
$processes = [];
$pipes = [];
foreach ($servers as $index => $server) {
$command = sprintf(
'sshpass -p %s ssh -o StrictHostKeyChecking=no %s@%s "%s"',
escapeshellarg($server['password']),
escapeshellarg($server['username']),
escapeshellarg($server['host']),
escapeshellarg(implode(' && ', $commands))
);
$processes[$index] = proc_open(
$command,
[
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
],
$pipes[$index]
);
}
// 读取结果
foreach ($processes as $index => $process) {
$stdout = stream_get_contents($pipes[$index][1]);
$stderr = stream_get_contents($pipes[$index][2]);
fclose($pipes[$index][0]);
fclose($pipes[$index][1]);
fclose($pipes[$index][2]);
$exitCode = proc_close($process);
$result = [
'server' => $servers[$index]['host'],
'stdout' => $stdout,
'stderr' => $stderr,
'exit_code' => $exitCode
];
$this->results[] = $result;
if ($callback) {
$callback($result);
}
}
return $this->results;
}
}
使用 Symfony Process 组件(推荐生产环境)
<?php
require_once 'vendor/autoload.php';
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
class SymfonySSHExecutor
{
private $results = [];
public function executeWithProcessBuilder($servers, $commands)
{
$processes = [];
foreach ($servers as $index => $server) {
$sshCommand = sprintf(
'ssh %s@%s "%s"',
$server['username'],
$server['host'],
implode(' && ', $commands)
);
$process = new Process(['bash', '-c', $sshCommand]);
$process->setTimeout(3600);
$process->setIdleTimeout(60);
$processes[$index] = $process;
}
// 并行执行
foreach ($processes as $index => $process) {
try {
$process->mustRun();
$this->results[] = [
'server' => $servers[$index]['host'],
'status' => 'success',
'output' => $process->getOutput(),
'error_output' => $process->getErrorOutput()
];
} catch (ProcessFailedException $exception) {
$this->results[] = [
'server' => $servers[$index]['host'],
'status' => 'failure',
'error' => $exception->getMessage()
];
}
}
return $this->results;
}
public function exportToCSV($filename = 'ssh_results.csv')
{
$fp = fopen($filename, 'w');
// CSV头部
fputcsv($fp, ['Server', 'Status', 'Output', 'Error']);
foreach ($this->results as $result) {
fputcsv($fp, [
$result['server'],
$result['status'],
$result['output'] ?? '',
$result['error'] ?? ''
]);
}
fclose($fp);
return $filename;
}
}
结果汇总与展示
<?php
class ResultAggregator
{
public static function aggregate($results)
{
$aggregated = [
'total_commands' => 0,
'successful' => 0,
'failed' => 0,
'errors' => [],
'servers_summary' => []
];
foreach ($results as $serverResult) {
$serverKey = $serverResult['server'];
if (!isset($aggregated['servers_summary'][$serverKey])) {
$aggregated['servers_summary'][$serverKey] = [
'total' => 0,
'success' => 0,
'fail' => 0
];
}
if ($serverResult['status'] === 'success') {
$aggregated['successful']++;
$aggregated['servers_summary'][$serverKey]['success']++;
} else {
$aggregated['failed']++;
$aggregated['servers_summary'][$serverKey]['fail']++;
$aggregated['errors'][] = $serverResult['error'];
}
$aggregated['total_commands']++;
$aggregated['servers_summary'][$serverKey]['total']++;
}
return $aggregated;
}
public static function renderHTML($results)
{
$html = '<table border="1">';
$html .= '<tr><th>Server</th><th>Command</th><th>Status</th><th>Output</th></tr>';
foreach ($results as $result) {
$statusClass = $result['status'] === 'success' ? 'success' : 'error';
$html .= sprintf(
'<tr class="%s"><td>%s</td><td>%s</td><td>%s</td><td><pre>%s</pre></td></tr>',
$statusClass,
htmlspecialchars($result['server']),
htmlspecialchars($result['command'] ?? 'N/A'),
htmlspecialchars($result['status']),
htmlspecialchars($result['output'] ?? $result['error'] ?? '')
);
}
$html .= '</table>';
return $html;
}
}
推荐使用场景
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| phpseclib | 无SSH2扩展的环境 | 纯PHP实现,兼容性好 | 性能相对较慢 |
| SSH2扩展 | 高性能需求 | 速度快,原生支持 | 需要安装扩展 |
| 并行执行 | 大规模服务器集群 | 效率高 | 资源消耗大 |
| Symfony Process | 生产环境 | 功能完善,异常处理好 | 依赖包较重 |
建议
- 生产环境:建议使用
phpseclib或Symfony Process组件 - 性能优化:对大量服务器使用并行执行
- 错误处理:一定要捕获并记录SSH连接失败、命令执行失败等异常
- 安全考虑:使用密钥认证代替密码,或使用SSH配置管理工具
- 结果持久化:可以将结果保存到数据库或文件系统中