英文:
How can I send terminal escape sequences through SSH with Go?
问题
我正在编写一个Go程序,使用本地的x/crypto/ssh库通过SSH连接到主机并启动一个交互式shell。
我正在使用RequestPty()
函数,但是远程主机上的(bash)shell在处理控制字符时表现不符合预期。
当我输入各种控制字符时,终端会将它们回显出来:
$ ^[[A
这些字符仍然有效,也就是说,如果我在按上箭头后按下回车键,前一个命令会被执行 - 但是控制字符的输出会覆盖本应显示的内容。Tab键也是一样的情况。
有没有一种简单的方法可以解决这个问题?在过去,当我实现类似的系统时,这不是一个问题,因为我只是调用了openssh
,而进程组的语义可以解决所有问题。
我已经阅读了《TTY解密》,虽然它很棒,但是不清楚从哪里开始。
我考虑了一些调查的方向:
- 在我的终端中启用原始模式 - 但是我不想在不理解原因的情况下这样做,而且它似乎不会产生正确的效果
- 调整终端模式标志
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-256color
和vt100
。
值得一提的是,在实际代码中,我不仅仅调用session.Wait()
,而是使用了一个for/select
循环来捕获进程的各种信号并将它们发送到Session
对象。
英文:
I'm writing a Go program that will connect to a host via SSH using the native x/crypto/ssh library and drop an interative shell.
I'm using RequestPty()
, but the (bash) shell on the remote end does not behave as expected with control codes.
When I enter various control characters, they're echoed back by my terminal:
$ ^[[A
The characters still work in the sense that if I press enter after pressing the up arrow, the previous command is run - but the control character output clobbers what should be displayed there. The same goes for tab.
Is there some straightforward way to get this to work? When I've implemented similar systems in the past it hasn't been an issue because I've just shelled out to openssh
, and the semantics of process groups sort it all out.
I've studied "The TTY Demystified" and as great as it is it's not clear where to begin.
A couple of things I've thought to investigate:
- enable raw mode in my terminal - but I don't want to do this without understanding why, and it doesn't seem to do the right thing
- fiddle with the terminal mode flags
openssh
itself must be doing this work correctly, but it's a real best of a code base to study.
It's not actually clear to me whether this printing is being done by my local terminal emulator or shell or by the code on the remote host.
Where do I begin?
Here is a sample of my code:
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()
I've tried this with term values other than xterm
: screen-256color
and vt100
.
For the record - in the real code, instead of just a call to session.Wait()
, I have a for/select
loop that catches various signals to the process and sends them on to the Session
.
答案1
得分: 17
ssh.ECHO: 0
和 ssh.ECHOCTL: 0
这两个设置对我来说没有起作用。对于遇到这个问题的其他人,以下是使用Go ssh库获取完全可交互终端所需的大致代码:
config := &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, // 启用回显
ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 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 { ... }
// 现在你应该通过SSH连接到一个完全可交互的终端
// 此调用会阻塞,直到用户退出会话(例如通过CTRL + D)
session.Wait()
请注意,上述设置可以正确地使用键盘功能(如制表符补全、向上箭头)和信号处理(CTRL + C
、CTRL + D
)。
英文:
The ssh.ECHO: 0
and ssh.ECHOCTL: 0
settings didn't work for me. For other people that ran into this issue, below is the rough code it took to get a fully working interactive terminal with the Go ssh library:
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()
Note that all keyboard functionality (tab completion, up arrow) and signal handling (CTRL + C
, CTRL + D
) works correctly with the setup above.
答案2
得分: 1
禁用ECHOCTL
终端模式。
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.ECHOCTL: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400
}
英文:
Disable ECHOCTL
terminal mode.
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.ECHOCTL: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论