如何使用 Go 通过 SSH 发送终端转义序列?

Posted

技术标签:

【中文标题】如何使用 Go 通过 SSH 发送终端转义序列?【英文标题】:How can I send terminal escape sequences through SSH with Go? 【发布时间】:2015-05-09 09:26:34 【问题描述】:

我正在编写一个 Go 程序,它将使用本机 x/crypto/ssh 库通过 SSH 连接到主机并删除一个交互式 shell。

我正在使用RequestPty(),但远程端的 (bash) shell 与控制代码的行为不符。

当我输入各种控制字符时,它们会被我的终端回显:

$ ^[[A

字符仍然工作,如果我在按下向上箭头后按下回车,前一个命令就会运行——但是控制字符输出会破坏应该在那里显示的内容。选项卡也是如此。

有没有一些简单的方法可以让它工作?当我过去实现类似的系统时,这不是问题,因为我刚刚向openssh 支付了费用,并且进程组的语义将其全部整理出来。

我已经研究过“The TTY Demystified”,尽管它很棒,但不清楚从哪里开始。

我想调查的几件事:

enable raw mode in my terminal - 但我不想在不理解原因的情况下这样做,而且它似乎做正确的事 fiddle with the terminal mode flags

openssh 本身必须正确地完成这项工作,但它是一个真正值得研究的代码库。

实际上我并不清楚这个打印是由我的本地终端模拟器或 shell 完成的,还是由远程主机上的代码完成的。

我从哪里开始?


这是我的代码示例:

conf := ssh.ClientConfig
    User: myuser,
    Auth: []ssh.AuthMethodssh.Password(my_password)

conn, err := ssh.Dial("tcp", myhost, conf)
if err != nil 
    return err

defer conn.Close()
session, err := conn.NewSession()
if err != nil 
    return err

defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin

modes := ssh.TerminalModes
    ssh.ECHO: 0
    ssh.TTY_OP_ISPEED: 14400,
    ssh.TTY_OP_OSPEED: 14400

if err := session.RequestPty("xterm", 80, 40, modes); err != nil 
    return err

if err = session.Shell(); err != nil 
    return err


return session.Wait()

我已经尝试使用除xterm 以外的术语值:screen-256colorvt100

记录一下 - 在实际代码中,我有一个 for/select 循环来捕获进程的各种信号并将它们发送到 Session,而不仅仅是调用 session.Wait()

【问题讨论】:

你试过禁用ECHOCTL终端模式吗? @EmilDavtyan 修复了它!现在明白为什么了。 @Cerales 请记住回答您自己的问题并接受答案:) @Cerales 我将我的评论作为答案发布,因此您可以将其标记为已回答。 @Cera:for/select 你在等待什么信号?当你得到它们时,你对它们做了什么?我似乎无法让SIGINT (CTRL+C) 以我期望的方式工作。 【参考方案1】:

ssh.ECHO: 0ssh.ECHOCTL: 0 设置对我不起作用。对于遇到此问题的其他人,以下是使用 Go ssh 库获得完全工作的交互式终端所需的粗略代码:

config := return &ssh.ClientConfig
    User: "username",
    Auth: []ssh.AuthMethodssh.Password("password"),


client, err := ssh.Dial("tcp", "12.34.56.78:22", config)
if err != nil  ... 
defer client.Close()

session, err := client.NewSession()
if err != nil  ... 
defer session.Close()

session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin

modes := ssh.TerminalModes
    ssh.ECHO:          1,     // enable echoing
    ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
    ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud


fileDescriptor := int(os.Stdin.Fd())

if terminal.IsTerminal(fileDescriptor) 
    originalState, err := terminal.MakeRaw(fileDescriptor)
    if err != nil  ... 
    defer terminal.Restore(fileDescriptor, originalState)

    termWidth, termHeight, err := terminal.GetSize(fileDescriptor)
    if err != nil  ... 

    err = session.RequestPty("xterm-256color", termHeight, termWidth, modes) 
    if err != nil  ... 


err = session.Shell()
if err != nil  ... 

// You should now be connected via SSH with a fully-interactive terminal
// This call blocks until the user exits the session (e.g. via CTRL + D)
session.Wait()

请注意,所有键盘功能(制表符补全、向上箭头)和信号处理(CTRL + CCTRL + D)在上述设置下都能正常工作。

【讨论】:

所以基本上问题是os.Stdin 正在以某种方式被缓冲?感谢这个顺便说一句,我在试图弄清楚如何解决这个问题时感到非常沮丧。【参考方案2】:

禁用ECHOCTL终端模式。

modes := ssh.TerminalModes
    ssh.ECHO: 0,
    ssh.ECHOCTL: 0,
    ssh.TTY_OP_ISPEED: 14400,
    ssh.TTY_OP_OSPEED: 14400

【讨论】:

这对我不起作用。我仍然看到字符转义码和自动完成功能不起作用。 我在我的代码中将ssh.ECHOCTL 设置为 0,当我使用箭头键时仍然可以看到控制字符。有什么想法吗?

以上是关于如何使用 Go 通过 SSH 发送终端转义序列?的主要内容,如果未能解决你的问题,请参考以下文章

[C#, 笔记] 启用虚拟终端处理 (使用 ANSI 转义序列前需启用)

通过 Ruby Telnet 发送转义序列

ctrl-x 在终端中使用时会发送哪个信号?

GO语言使用开源SSH模拟终端

通过 ssh 从 ssh-remote 服务器将终端命令发送回本地?

ANSI 颜色转义序列列表