Feat: 使用Shell模式执行命令

- 实现ExecuteCommands使用Shell模式,在同一个会话中顺序执行
- 解决H3C设备session.Run后EOF断开问题
- 增加cleanCommandOutput清理命令回显和版权信息
- display interface等待5秒,其他命令等待2秒
This commit is contained in:
Your Name
2026-04-26 03:19:10 +08:00
parent 4edc1b67fc
commit 87d233659c
2 changed files with 138 additions and 26 deletions
+11 -18
View File
@@ -77,24 +77,17 @@ func DiscoverDevice(ip string, deviceType models.DeviceType, username, password
// 获取命令列表 // 获取命令列表
commands := parser.GetCommands() commands := parser.GetCommands()
// 执行命令 - 允许部分命令失败,增加详细日志和延迟防止设备速率限制 // 执行所有命令(使用Shell模式,在同一个会话中顺序执行)
outputs := make([]string, 0, len(commands)) outputs, err := client.ExecuteCommands(commands)
for i, cmd := range commands { if err != nil {
// 每个命令之间等待2秒,防止H3C交换机速率限制导致返回空数据或执行失败 device.ScanStatus = "failed"
if i > 0 { device.ErrorMessage = err.Error()
time.Sleep(2 * time.Second) return device, err
} }
fmt.Printf("[PARSER] Executing command %d/%d: %s\n", i+1, len(commands), cmd) // 打印调试信息
output, err := client.ExecuteCommand(cmd) for i, output := range outputs {
if err != nil { fmt.Printf("[PARSER] Command %d/%d returned %d bytes\n", i+1, len(commands), len(output))
// 记录警告但继续执行其他命令
fmt.Printf("Warning: command '%s' failed: %v\n", cmd, err)
outputs = append(outputs, "")
} else {
fmt.Printf("[PARSER] Command '%s' returned %d bytes\n", cmd, len(output))
outputs = append(outputs, output)
}
} }
// 解析输出 // 解析输出
+127 -8
View File
@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"regexp"
"strings"
"time" "time"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@@ -140,19 +142,136 @@ func (c *Client) ExecuteCommand(command string) (string, error) {
return stdoutBuf.String(), nil return stdoutBuf.String(), nil
} }
// ExecuteCommands 执行多个命令 // ExecuteCommands 执行多个命令(使用Shell模式,在同一个会话中顺序执行)
func (c *Client) ExecuteCommands(commands []string) ([]string, error) { func (c *Client) ExecuteCommands(commands []string) ([]string, error) {
results := make([]string, 0, len(commands)) if c.client == nil {
for _, cmd := range commands { return nil, fmt.Errorf("not connected")
result, err := c.ExecuteCommand(cmd)
if err != nil {
return results, fmt.Errorf("failed to execute command '%s': %w", cmd, err)
}
results = append(results, result)
} }
// 创建一个shell会话
session, err := c.client.NewSession()
if err != nil {
return nil, fmt.Errorf("failed to create session: %w", err)
}
defer session.Close()
// 请求PTY
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 禁用回显
ssh.TTY_OP_ISPEED: 14400, // 输入速度
ssh.TTY_OP_OSPEED: 14400, // 输出速度
}
if err := session.RequestPty("dumb", 200, 1000, modes); err != nil {
return nil, fmt.Errorf("failed to request pty: %w", err)
}
// 获取stdin管道
stdin, err := session.StdinPipe()
if err != nil {
return nil, fmt.Errorf("failed to get stdin pipe: %w", err)
}
// 捕获输出
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
// 启动shell
if err := session.Shell(); err != nil {
return nil, fmt.Errorf("failed to start shell: %w", err)
}
// 执行命令并收集输出
results := make([]string, 0, len(commands))
for i, cmd := range commands {
// 等待一段时间防止设备速率限制
if i > 0 {
time.Sleep(2 * time.Second)
}
// 发送命令(添加换行符)
if _, err := stdin.Write([]byte(cmd + "\n")); err != nil {
return results, fmt.Errorf("failed to send command '%s': %w", cmd, err)
}
// 等待命令执行完成(不同命令需要不同等待时间)
sleepTime := 1 * time.Second
if cmd == "display interface" {
sleepTime = 5 * time.Second // 大输出命令需要更多时间
}
time.Sleep(sleepTime)
// 获取当前输出并清理
rawOutput := stdoutBuf.String()
cleanOutput := cleanCommandOutput(rawOutput, cmd)
results = append(results, cleanOutput)
// 清空缓冲区,为下一个命令做准备(通过位置标记)
stdoutBuf.Reset()
stderrBuf.Reset()
}
// 退出shell
stdin.Write([]byte("exit\n"))
session.Wait()
return results, nil return results, nil
} }
// cleanCommandOutput 清理命令输出,移除命令回显、分页提示和提示符
func cleanCommandOutput(output, command string) string {
// 清理\r\n为\n
output = strings.ReplaceAll(output, "\r\n", "\n")
lines := strings.Split(output, "\n")
var cleanLines []string
skipCommandEcho := true // 跳过命令本身的回显
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
// 跳过空行(如果是开头)
if trimmedLine == "" && len(cleanLines) == 0 {
continue
}
// 跳过命令回显(第一次出现)
if skipCommandEcho && trimmedLine == strings.TrimSpace(command) {
skipCommandEcho = false
continue
}
// 跳过分页提示
if strings.Contains(trimmedLine, "---- More ----") {
continue
}
// 跳过提示符行(如 <hostname> 或 [hostname]
if regexp.MustCompile(`^[<\[]\S+[>\]]$`).MatchString(trimmedLine) {
continue
}
// 跳过版权信息(开头)
if strings.HasPrefix(trimmedLine, "*********") {
continue
}
if strings.HasPrefix(trimmedLine, "* Copyright") {
continue
}
if strings.HasPrefix(trimmedLine, "* Without") {
continue
}
if strings.HasPrefix(trimmedLine, "* no decompiling") {
continue
}
cleanLines = append(cleanLines, trimmedLine)
}
return strings.Join(cleanLines, "\n")
}
// CheckSSH 检查主机是否开启SSH // CheckSSH 检查主机是否开启SSH
func CheckSSH(host string, port int, timeout time.Duration) bool { func CheckSSH(host string, port int, timeout time.Duration) bool {
if port == 0 { if port == 0 {