Feat: 使用Shell模式执行命令
- 实现ExecuteCommands使用Shell模式,在同一个会话中顺序执行 - 解决H3C设备session.Run后EOF断开问题 - 增加cleanCommandOutput清理命令回显和版权信息 - display interface等待5秒,其他命令等待2秒
This commit is contained in:
+11
-18
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user