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

时间:2015-03-07 23:41:34

标签: bash ssh go tty

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

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

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

$ ^[[A

字符仍然工作,因为如果按下向上箭头后按Enter键,则会运行上一个命令 - 但是控制字符输出会破坏应该在那里显示的内容。标签也是如此。

是否有一些简单的方法可以让它发挥作用?当我在过去实现类似的系统时,它不是一个问题,因为我刚刚向openssh提出了问题,并且进程组的语义将它全部排除。

我已经研究过“The TTY Demystified”,但是它还不清楚从哪里开始。

我想要调查的几件事情:

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

我真的不清楚这个打印是由我的本地终端仿真器或shell还是由远程主机上的代码完成的。

我从哪里开始?


以下是我的代码示例:

conf := ssh.ClientConfig{
    User: myuser,
    Auth: []ssh.AuthMethod{ssh.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

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

2 个答案:

答案 0 :(得分:7)

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

config := return &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{ssh.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)都能正常使用上述设置。

答案 1 :(得分:1)

禁用ECHOCTL终端模式。

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