
本教程深入探讨了使用 go 语言 `ssh` 库连接 cisco 设备时,发送长命令可能遭遇的截断问题。当通过 ssh shell 传输较长命令时,由于伪终端 (pty) 尺寸配置不当,命令可能在设备端被意外分割,导致执行失败。文章详细分析了问题根源,并提供了通过正确设置 `requestpty` 参数来解决命令截断的实用方法,确保与网络设备的顺畅通信。
在网络自动化和监控场景中,Go 语言因其并发特性和丰富的标准库而备受青睐。通过 Go 的 ssh 库与网络设备(如 Cisco 路由器)进行交互是常见的需求。然而,与传统服务器不同,许多网络设备(特别是旧款或特定配置的设备)通常不直接支持通过 ssh.Session.Run() 执行命令,而是需要通过交互式 Shell 会话来发送命令并读取输出。在这种模式下,正确配置 SSH 会话的伪终端(PTY)至关重要,否则可能导致意想不到的问题,例如长命令被截断。
首先,我们需要使用 Go 的 golang.org/x/crypto/ssh 库建立与 Cisco 设备的 SSH 连接,并创建一个交互式 Shell 会话。
package main
import (
"bytes"
"fmt"
"io"
"log"
"strings"
"time"
"golang.org/x/crypto/ssh"
)
// SSH配置示例
func getSSHConfig() *ssh.ClientConfig {
return &ssh.ClientConfig{
User: "your_username",
Auth: []ssh.AuthMethod{
ssh.Password("your_password"),
},
HostKeyCallback: ssh.InsecureHostKeyCallback(), // 生产环境请使用更安全的HostKeyCallback
Timeout: 10 * time.Second,
}
}
// 连接并获取Shell会话
func setupSSHSession(addr string, config *ssh.ClientConfig) (*ssh.Client, *ssh.Session, io.Writer, io.Reader, error) {
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("无法连接SSH服务器: %v", err)
}
session, err := client.NewSession()
if err != nil {
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法创建SSH会话: %v", err)
}
// 获取输入输出管道
sshOut, err := session.StdoutPipe()
if err != nil {
session.Close()
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法获取StdoutPipe: %v", err)
}
sshIn, err := session.StdinPipe()
if err != nil {
session.Close()
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法获取StdinPipe: %v", err)
}
// 请求PTY,这里是关键点,稍后会详细讨论
modes := ssh.TerminalModes{
ssh.ECHO: 0, // 禁用回显
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// 初始请求PTY,这里可能存在问题
// session.RequestPty("xterm", 80, 40, modes) // 假设原始代码中的错误配置:高度80,宽度40
// 修正后的配置将在下文给出
err = session.RequestPty("vt100", 200, 80, modes) // 临时使用一个相对合理的默认值
if err != nil {
session.Close()
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法请求PTY: %v", err)
}
err = session.Shell()
if err != nil {
session.Close()
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法启动Shell: %v", err)
}
return client, session, sshIn, sshOut, nil
}在 Shell 启动后,设备通常会输出一些欢迎信息、上次登录记录等,直到显示命令提示符。我们需要读取并忽略这些信息,直到识别出提示符,才能开始发送命令。
// 读取直到找到特定提示符
func readUntilPrompt(reader io.Reader, prompt string) (string, error) {
var buffer bytes.Buffer
buf := make([]byte, 1024)
for {
n, err := reader.Read(buf)
if err != nil {
if err == io.EOF {
break // 连接关闭
}
return "", fmt.Errorf("读取SSH输出失败: %v", err)
}
buffer.Write(buf[:n])
output := buffer.String()
// 检查是否包含提示符,Cisco设备提示符可能包含设备名、模式等
// 示例中的 "[local]ewag#" 提示符
if strings.Contains(output, prompt) {
return output, nil
}
// 避免无限循环,设置一个超时机制
// 实际应用中需要更复杂的超时和错误处理
if buffer.Len() > 4096 { // 超过一定长度仍未找到提示符,可能出错
return output, fmt.Errorf("未在预期时间内找到提示符 '%s'", prompt)
}
time.Sleep(100 * time.Millisecond) // 短暂等待,避免CPU空转
}
return buffer.String(), nil
}对于短命令,通常不会出现问题。以下是发送 show clock 命令并读取其输出的示例:
// 发送命令
func sendCommand(writer io.Writer, command string) error {
_, err := writer.Write([]byte(command + "\r")) // \r 表示回车
if err != nil {
return fmt.Errorf("发送命令失败: %v", err)
}
return nil
}
func main() {
addr := "172.16.32.95:22" // 替换为你的Cisco设备IP和端口
prompt := "[local]ewag#" // 替换为你的设备实际提示符
client, session, sshIn, sshOut, err := setupSSHSession(addr, getSSHConfig())
if err != nil {
log.Fatalf("设置SSH会话失败: %v", err)
}
defer session.Close()
defer client.Close()
fmt.Println("等待初始提示符...")
initialOutput, err := readUntilPrompt(sshOut, prompt)
if err != nil {
log.Fatalf("读取初始提示符失败: %v", err)
}
fmt.Printf("初始输出:\n%s\n", initialOutput)
fmt.Println("发送短命令 'show clock'...")
err = sendCommand(sshIn, "show clock")
if err != nil {
log.Fatalf("发送 'show clock' 命令失败: %v", err)
}
clockOutput, err := readUntilPrompt(sshOut, prompt)
if err != nil {
log.Fatalf("读取 'show clock' 结果失败: %v", err)
}
fmt.Printf("短命令输出:\n%s\n", clockOutput)
// ... 接下来将讨论长命令的问题
}当尝试发送一个较长的命令时,例如 show session progress ipsg-service ipsg-gprs-svc,我们可能会发现命令在设备端被意外地分割,导致设备报告“未知命令”或“无法识别的关键字”。
// ... (main函数中,在发送短命令之后)
fmt.Println("发送长命令 'show session progress ipsg-service ipsg-gprs-svc'...")
err = sendCommand(sshIn, "show session progress ipsg-service ipsg-gprs-svc")
if err != nil {
log.Fatalf("发送长命令失败: %v", err)
}
longCommandOutput, err := readUntilPrompt(sshOut, prompt)
if err != nil {
log.Fatalf("读取长命令结果失败: %v", err)
}
fmt.Printf("长命令输出:\n%s\n", longCommandOutput)
// 预期输出可能类似:
// show session progress ipsg
// -service ipsg-gprs-svc
// Unknown command - "ipsg-service", unrecognized keyword
// [local]ewag#从上述输出可以看出,命令 show session progress ipsg-service ipsg-gprs-svc 在 ipsg 之后被截断并换行,导致设备将其识别为两个独立的、不完整的命令部分,从而引发错误。
这个问题的根源在于 session.RequestPty 函数的参数配置。RequestPty 用于请求一个伪终端 (Pseudo-Terminal),其签名通常为 RequestPty(term string, h, w int, termmodes TerminalModes) error,其中 h 是终端的高度(行数),w 是终端的宽度(列数)。
许多网络设备在处理交互式 Shell 输入时,会根据 PTY 的宽度参数来限制单行输入缓冲的大小。如果 RequestPty 中设置的宽度 w 过小,当发送的命令字符串长度超过这个宽度时,设备可能会将其视为多行输入,或者在达到宽度限制时自动换行,从而导致命令被截断。
Whimsical
Whimsical推出的AI思维导图工具
182
查看详情
在原始问题中,可能存在两种情况导致宽度不足:
解决此问题的关键是确保在 session.RequestPty 调用中提供一个足够大的宽度值。一个常见的做法是设置一个较大的固定值(例如 200 或 300),或者将宽度设置为 0,让终端自动协商或使用最大允许宽度(这取决于 SSH 服务器的实现)。
将 setupSSHSession 函数中的 PTY 请求行修改为:
// 修正后的PTY请求,确保宽度足够
// height=0, width=200 通常能解决问题,0 表示让终端自动调整或使用默认值
err = session.RequestPty("vt100", 0, 200, modes)
if err != nil {
session.Close()
client.Close()
return nil, nil, nil, nil, fmt.Errorf("无法请求PTY: %v", err)
}通过将宽度设置为 200(或 0),我们为 Shell 输入提供了更大的缓冲区,从而避免了长命令被意外截断的问题。修改后的代码将能够正确地发送和执行长命令。
以下是 setupSSHSession 函数更新后的核心代码片段:
// 连接并获取Shell会话 (更新PTY请求) func setupSSHSession(addr string, config *ssh.ClientConfig) (*ssh.Client, *ssh.Session, io.Writer, io.Reader, error) { // ... (省略SSH连接和会话创建部分,与之前相同) // 获取输入输出管道 sshOut, err := session.StdoutPipe() if err != nil { session.Close() client.Close() return nil, nil, nil, nil, fmt.Errorf("无法获取StdoutPipe: %v", err) } sshIn, err := session.StdinPipe() if err != nil { session.Close() client.Close() return nil, nil, nil, nil, fmt.Errorf("无法获取StdinPipe: %v", err) } // 请求PTY,修正宽度参数 modes := ssh.TerminalModes{ ssh.ECHO: 0, // 禁用回显 ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud } // 关键修正:将宽度设置为一个足够大的值 (例如 200) 或 0 (让服务器决定) // term: "vt100" 是一个通用的终端类型 // height: 0 (或任何合理值,如 24, 80) // width: 200 (确保命令不会被截断) err = session.RequestPty("vt100", 0, 200, modes) if err != nil { session.Close() client.Close() return nil, nil, nil, nil, fmt.Errorf("无法请求PTY: %v", err) } err = session.Shell() if err != nil { session.Close() client.Close() return nil, nil, nil, nil, fmt.Errorf("无法启动Shell: %v", err) } return client, session, sshIn, sshOut, nil }
通过 Go 语言的 ssh 库与 Cisco 等网络设备进行交互时,如果遇到长命令被截断的问题,很可能是由于 session.RequestPty 函数中伪终端 (PTY) 的宽度参数配置不当所致。理解 RequestPty 参数的含义,并确保为终端宽度提供一个足够大的值(例如 200 或 0),是解决此类问题的关键。遵循这些最佳实践,可以确保你的 Go 自动化脚本与网络设备之间建立稳定、可靠的交互。
以上就是Go SSH 连接 Cisco 设备时命令被截断的深度解析与修复的详细内容,更多请关注其它相关文章!
相关文章:
win11如何加载ICC颜色配置文件 Win11校色文件安装与显示器色彩管理【指南】
c++中为什么推荐使用using替代typedef_c++现代化类型别名
Composer的 "licenses" 命令如何帮助你遵守开源协议_检查项目依赖的许可证合规性
拼多多购物车商品数量无法修改如何处理 拼多多购物车操作优化方法
如何在低配置电脑上搭建轻量级J*a环境_占用更小的环境选择技巧
邮政快递单号查询入口 邮政快递物流信息在线查询入口
从J*aScript对象中精确提取指定属性的教程
qq浏览器如何查看和导出已保存的密码 qq浏览器密码管理器数据备份教程
css绝对定位元素脱离父容器怎么办_确保父元素position非static
Mac怎么锁定备忘录_Mac备忘录加密设置教程
在J*a中如何使用Stream.map转换元素_Stream映射操作解析
必由学官方登录入口 必由学教师学生账号快速访问
Walmart退货API集成指南:PHP cURL实现与常见问题解析
CSS Box Model与弹性按钮:维持布局稳定的动画实践
Golang如何实现状态模式管理对象状态_Golang State模式实现技巧
优化MinIO list_objects_v2 操作的性能瓶颈与最佳实践
Typer应用中动态命令行参数的解析与处理
漫蛙manwa官网登录界面_漫蛙漫画网页版主站入口
html网页设计源代码怎么运行_运行html网页设计源代码步骤【指南】
新三国志曹操传110级星符试炼夏侯渊极难攻略
蛙漫画网页版全站入口 蛙漫热门作品免费浏览
QQ邮箱官网登录入口 QQ邮箱网页版邮箱快速登录
Python实现多节点属性重叠度分析教程
Mudbox图层蒙版怎么用_Mudbox图层蒙版数字雕刻应用技巧
AO3网页版合集入口 Archive of Our Own同人作品浏览指南
UC浏览器如何安装插件 UC浏览器添加扩展程序详细教程【进阶】
如何创建独立于主系统的J*a运行环境_隔离式环境搭建策略
如何在 Windows 11 中启动游戏手柄设置
手机CPU怎么影响游戏体验_手机CPU对游戏性能的影响分析
2306选座时如何选靠窗位置_12306选座靠窗座位查看方法解析
荒野行动PC版怎么注册_荒野行动PC版账号注册详细流程图文教程
Windows10怎么开启存储感知 Windows10系统设置自动清理临时文件释放C盘空间【教程】
邮编格式怎么匹配地址_根据邮编格式快速匹配详细地址的技巧
C#如何安全地从用户上传的XML文件中读取数据? 验证与清理策略
Django通过AJAX异步上传图片并保存至模型的完整指南
小米14应用无法联网原因分析_小米14网络权限修复
cad怎么合并重叠的线段_cad清理重复重叠线条的操作方法
Angular响应式表单:实现提交后表单及按钮的禁用与只读化
小红书网页版入口链接分享 小红书官网直接进
J*aScript实现动态背景色下的文本与按钮颜色自适应调整
电脑安装程序提示“错误1722”怎么办_Windows Installer服务问题解决【教程】
虚幻5科幻题材ARPG大作遭取消!本是《奇异人生》厂商新作
QQ邮箱网页版邮箱入口 QQ邮箱官方登录平台
QQ邮箱在线使用入口 QQ邮箱个人账号网页版登录
实现分段式页面滚动导航:CSS与J*aScript教程
抖音小游戏合成大西瓜免费秒玩入口链接 抖音小游戏热门合集秒玩网站
在哪找SublimeJ远程工具_SFTP插件配置教程
css链接悬停下划线样式如何自定义_使用::after结合content和transition
支付宝解绑银行卡步骤_支付宝如何解除绑定银行卡
利用Bokeh CustomJS动态控制DataTable列可见性