From 945695d210327037654cbfadafc1b075e8c6af7e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 26 Apr 2026 01:39:01 +0800 Subject: [PATCH] =?UTF-8?q?Feat:=20=E6=94=AF=E6=8C=81H3C/=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E8=AE=BE=E5=A4=87=E5=88=86=E9=A1=B5=E7=A6=81=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用Shell模式替代Run模式执行命令 - 自动发送screen-length disable禁用分页 - 清理输出中的命令回显和分页提示 - 解决display interface输出为空的问题 --- internal/ssh/client.go | 90 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/internal/ssh/client.go b/internal/ssh/client.go index e3534fb..c54ae55 100644 --- a/internal/ssh/client.go +++ b/internal/ssh/client.go @@ -5,7 +5,9 @@ import ( "fmt" "net" "os" + "strings" "time" + "regexp" "golang.org/x/crypto/ssh" ) @@ -143,16 +145,92 @@ func (c *Client) ExecuteCommand(command string) (string, error) { } defer session.Close() + // H3C/华为设备需要先禁用分页 + // 使用 Shell 模式而不是 Run 模式 + 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 "", fmt.Errorf("failed to request pty: %w", err) + } + + // 启动 shell + shell, err := session.StdinPipe() + if err != nil { + return "", fmt.Errorf("failed to get stdin pipe: %w", err) + } + var stdoutBuf bytes.Buffer session.Stdout = &stdoutBuf session.Stderr = &stdoutBuf - - err = session.Run(command) - if err != nil { - return stdoutBuf.String(), fmt.Errorf("command execution failed: %w", err) + + if err := session.Shell(); err != nil { + return "", fmt.Errorf("failed to start shell: %w", err) } - - return stdoutBuf.String(), nil + + // 先发送禁用分页命令(H3C/华为) + if _, err := shell.Write([]byte("screen-length disable\n")); err != nil { + return "", fmt.Errorf("failed to send screen-length disable: %w", err) + } + time.Sleep(500 * time.Millisecond) // 等待命令执行 + + // 发送实际命令 + if _, err := shell.Write([]byte(command + "\n")); err != nil { + return "", fmt.Errorf("failed to send command: %w", err) + } + + // 等待命令执行完成 + time.Sleep(2 * time.Second) + if _, err := shell.Write([]byte("exit\n")); err != nil { + return "", fmt.Errorf("failed to send exit: %w", err) + } + + // 等待会话结束 + session.Wait() + + output := stdoutBuf.String() + + // 清理输出:移除命令回显和分页提示 + lines := strings.Split(output, "\n") + var cleanLines []string + skipNext := false + for _, line := range lines { + line = strings.TrimSpace(line) + + // 跳过空行 + if line == "" { + continue + } + + // 跳过分页提示 + if strings.Contains(line, "---- More ----") { + continue + } + + // 跳过命令本身的回显 + if line == command || line == "screen-length disable" { + skipNext = true + continue + } + + // 跳过提示符行(如 或 [hostname]) + if regexp.MustCompile(`^[<\[]\S+[>\]]$`).MatchString(line) { + continue + } + + // 如果是 "screen-length disable" 后的第一行(通常是提示符),跳过 + if skipNext { + skipNext = false + continue + } + + cleanLines = append(cleanLines, line) + } + + return strings.Join(cleanLines, "\n"), nil } // ExecuteCommands 执行多个命令