go的crypto/ssh库无client.do()方法,须用client.newsession()获取*ssh.session后调用run()、output()或start()+wait();文件上传应使用sftp.client而非session.run("scp")。

ssh.Client.Do() 不存在,别被文档误导
Go 的 crypto/ssh 标准库不提供类似 Python paramiko 的 exec_command() 或封装好的 Do() 方法。你不能直接调用 client.Do() —— 它根本不存在,编译会报 client.Do undefined。这是初学者最常卡住的第一步。
真实路径只有一条:用 client.NewSession() 拿到 *ssh.Session,再调用它的 Run()、Start() 或 Output()。Session 是有状态的,每次执行命令都得新建一个,复用 session 可能导致 write tcp: broken pipe。
-
Run():适合单次同步命令(如ls /tmp),自动等待退出,返回 exit code -
Output():同Run(),但额外帮你读取 stdout,适合获取结果(如cat config.json) -
Start()+Wait():需要 stdin/stderr 交互或长时间运行时用,但必须手动 close session 和管道
文件上传用 sftp.Client,不是 ssh.Session.Run("scp")
想传文件?别写 session.Run("scp file user@host:/path") —— 这在远端没有 scp 命令、shell 被限制(如 noshell)、或路径含空格时必然失败,且无法捕获传输进度和错误细节。
标准做法是用 golang.org/x/crypto/ssh/sshagent 配合 golang.org/x/crypto/ssh/sftp。sftp.Client 基于 SSH 协议的 SFTP 子系统,不依赖远端 shell 环境,权限更可控,也支持断点续传逻辑(需自己实现)。
立即学习“go语言免费学习笔记(深入)”;
- 先用
ssh.Client拨号,再调用ssh.NewClientConn()→sftp.NewClientConn()→sftp.NewClient() - 上传用
client.Create()+io.Copy(),注意设置os.FileMode(0644),否则默认可能是 0600 - 远端目录不存在?
client.MkdirAll()不能递归创建(旧版 sftp server 不支持),得自己拆路径逐级Mkdir()
权限拒绝、连接重置、EOF:90% 出在认证或配置上
常见错误如 ssh: handshake failed: ssh: unable to authenticate、read tcp: i/o timeout、unexpected EOF,基本和代码逻辑无关,而是 SSH 层配置没对齐。
重点检查三处:
- 私钥格式:OpenSSH 8.8+ 默认禁用 PEM,要求新格式(
-----BEGIN OPENSSH PRIVATE KEY-----)。老 key 要用ssh-keygen -p -f key.pem -m pem转换 - 用户权限:远端目标用户对目标目录必须有写权限;若用 root 执行命令,确保
PermitRootLogin yes且未被AllowUsers拦截 - 超时设置:
ssh.ClientConfig.Timeout控制握手超时,但连接后命令超时得靠session.Setenv("TERM", "dumb")+session.RequestPty(...)配合信号中断,或更稳妥地用context.WithTimeout()包裹session.Run()
执行带重定向或管道的命令要加 /bin/sh -c
像 echo hello > /tmp/a.txt 或 ps aux | grep nginx 这类命令,在 session.Run() 中直接传会失败 —— 因为 Run() 调用的是远端的 exec.Command,不经过 shell 解析,>、|、$PATH 扩展全无效。
必须显式调用 shell:
- 写成
/bin/sh -c "echo hello > /tmp/a.txt" - 避免双引号嵌套混乱:用
fmt.Sprintf(`/bin/sh -c %q`, cmd),%q自动转义 - 注意:不同系统默认 shell 不同(Alpine 用
/bin/ash),硬编码/bin/sh更兼容
复杂脚本建议写进临时文件再执行,比拼接字符串更可靠,也方便调试。










