不使用命令的情况下使用 PTY

huangapple go评论115阅读模式
英文:

Using a PTY without a command

问题

我在creack/pty的问题中提出了一个问题,但我实际上认为这个问题可能更适合在这里讨论,因为它可能与我对该库的使用有关,而不是库本身的问题。

我正在使用一个websocket API,该API发送stdin消息并接收命令的stdout、stderr输出以及退出代码。

例如,该API在Web UI中使用,发送和接收以下一系列消息:
不使用命令的情况下使用 PTY

这个Web UI使用xterm.js提供类似终端的输入界面,并将响应(包括ANSI转义序列)解释为终端输出。

我正在构建一个终端应用程序,希望利用相同的API,实现一个类似于“终端中的终端”的功能,其中stdin被发送到API,接收到的响应被渲染到我的应用程序的屏幕上。

所以大致的流程是:

  • 将stdin发送到websocket连接
  • 接收响应
  • 将响应写入pty
  • 将整个pty读取为字符串
  • 将字符串渲染到屏幕上
  • 重复

如果我以这种方式使用creack/pty,实际上我没有一个要启动的命令 - 它只是一个为我解释ANSI转义序列的好工具,并允许我检索终端的当前“字符串视图”。

这是我尝试获取一个无需命令的pty、写入它并从中读取的代码:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/creack/pty"
  6. "io"
  7. "os"
  8. )
  9. func getPtyWithoutCommand() (*os.File, error) {
  10. // 这个函数只是pty.StartWithAttrs,其中与命令相关的内容被注释掉了
  11. pty, tty, err := pty.Open()
  12. if err != nil {
  13. return nil, err
  14. }
  15. defer func() { _ = tty.Close() }() // 尽力而为。
  16. // if sz != nil {
  17. // if err := Setsize(pty, sz); err != nil {
  18. // _ = pty.Close() // 尽力而为。
  19. // return nil, err
  20. // }
  21. // }
  22. // if c.Stdout == nil {
  23. // c.Stdout = tty
  24. // }
  25. // if c.Stderr == nil {
  26. // c.Stderr = tty
  27. // }
  28. // if c.Stdin == nil {
  29. // c.Stdin = tty
  30. // }
  31. //
  32. // c.SysProcAttr = attrs
  33. //
  34. // if err := c.Start(); err != nil {
  35. // _ = pty.Close() // 尽力而为。
  36. // return nil, err
  37. // }
  38. return pty, err
  39. }
  40. func main() {
  41. myPty, err := getPtyWithoutCommand()
  42. if err != nil {
  43. panic(err)
  44. }
  45. _, err = myPty.Write([]byte("test\n"))
  46. if err != nil {
  47. panic(err)
  48. }
  49. _, err = myPty.Write([]byte{4}) // EOT
  50. if err != nil {
  51. panic(err)
  52. }
  53. buf := new(bytes.Buffer)
  54. _, err = io.Copy(buf, myPty)
  55. if err != nil {
  56. panic(err)
  57. }
  58. fmt.Println(buf.String())
  59. }

我得到以下错误:

  1. go run test.go
  2. panic: write /dev/ptmx: input/output error
  3. goroutine 1 [running]:
  4. main.main()
  5. test.go:52 +0x19c
  6. exit status 2

我正在尝试的做法是否合理?在这里实现我的目标是否有更好的方法?

英文:

I opened an issue in creack/pty for this question, but I actually think it probably belongs here as it's probably more to do with my usage of the library than anything wrong with the library.

I am using a websocket api that sends stdin messages and receives stdout and stderr output from the command as well as exit codes.

For example, this API is used in a web UI, with the following string of messages sent and received
不使用命令的情况下使用 PTY

This web UI uses xterm.js to provide a terminal-like input ui and to interpret the responses, including ansi escape sequences, into terminal output.

I am building a terminal application that would like to leverage this same API, so a "terminal-in-a-terminal" like thing, where stdin is sent to the API and responses received are rendered in my application.

I would like to use creack/pty as the response interpreter, handling ansi escape sequences and the like, and holding a view of the terminal session that I can read into a string and render to the screen of my application.

So the flow is roughly like:

  • stdin sent to websocket connection
  • response received
  • write response to pty
  • read entire pty to string
  • render string to screen
  • repeat

If I use creack/pty this way, I don't actually have a command to start - it's just a nice box that interprets ansi escape sequences for me and allows me to retrieve the current "string view" of the terminal.

Here is my attempt to get a command-less pty, write to it, and read from it:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/creack/pty"
  6. "io"
  7. "os"
  8. )
  9. func getPtyWithoutCommand() (*os.File, error) {
  10. // this function just pty.StartWithAttrs with command-specific stuff commented out
  11. pty, tty, err := pty.Open()
  12. if err != nil {
  13. return nil, err
  14. }
  15. defer func() { _ = tty.Close() }() // Best effort.
  16. // if sz != nil {
  17. // if err := Setsize(pty, sz); err != nil {
  18. // _ = pty.Close() // Best effort.
  19. // return nil, err
  20. // }
  21. // }
  22. // if c.Stdout == nil {
  23. // c.Stdout = tty
  24. // }
  25. // if c.Stderr == nil {
  26. // c.Stderr = tty
  27. // }
  28. // if c.Stdin == nil {
  29. // c.Stdin = tty
  30. // }
  31. //
  32. // c.SysProcAttr = attrs
  33. //
  34. // if err := c.Start(); err != nil {
  35. // _ = pty.Close() // Best effort.
  36. // return nil, err
  37. // }
  38. return pty, err
  39. }
  40. func main() {
  41. myPty, err := getPtyWithoutCommand()
  42. if err != nil {
  43. panic(err)
  44. }
  45. _, err = myPty.Write([]byte("test\n"))
  46. if err != nil {
  47. panic(err)
  48. }
  49. _, err = myPty.Write([]byte{4}) // EOT
  50. if err != nil {
  51. panic(err)
  52. }
  53. buf := new(bytes.Buffer)
  54. _, err = io.Copy(buf, myPty)
  55. if err != nil {
  56. panic(err)
  57. }
  58. fmt.Println(buf.String())
  59. }

I get the following error

  1. go run test.go
  2. panic: write /dev/ptmx: input/output error
  3. goroutine 1 [running]:
  4. main.main()
  5. test.go:52 +0x19c
  6. exit status 2

Is what I'm trying to do sane at all? Is there a better way to achieve my goal here?

答案1

得分: 3

我正在处理一个类似的问题,creack的pty可能对你想要的内容有点过于抽象化了。我只是使用了Golang标准的xterm终端模拟器https://pkg.go.dev/golang.org/x/term和一个管道。

这是一个部分代码片段,作为我所说的示例。你的标准输入流被输入到stdin_writer中,你的标准输出被写入到writer中,不要忘记调用Flush()

  1. stdin_reader, stdin_writer := io.Pipe()
  2. reader := bufio.NewReader(stdin_reader)
  3. stdout_writer := bytes.Buffer{}
  4. writer := bufio.NewWriter(&stdout_writer)
  5. rw := bufio.NewReadWriter(reader, writer)
  6. t := term.NewTerminal(rw, prompt)
  7. // 不断读取行
  8. go func() {
  9. for {
  10. line, err := t.ReadLine()
  11. if err == io.EOF {
  12. log.Printf("收到EOF")
  13. }
  14. if err != nil {
  15. log.Printf("收到错误")
  16. }
  17. if line == "" {
  18. continue
  19. }
  20. log.Printf("行: %s", line)
  21. }
  22. }()
英文:

I was tackling a similar problem and creack's pty might be a bit too abstracted for what you want. I just used the golang standard xterm terminal emulator https://pkg.go.dev/golang.org/x/term and a pipe.

Here's a partial snippet as an example of what I mean. Your stdin stream is fed into the stdin_writer and your stdout is written to the writer, don't forget to Flush()!

  1. stdin_reader, stdin_writer := io.Pipe()
  2. reader := bufio.NewReader(stdin_reader)
  3. stdout_writer := bytes.Buffer{}
  4. writer := bufio.NewWriter(&stdout_writer)
  5. rw := bufio.NewReadWriter(reader, writer)
  6. t := term.NewTerminal(rw, prompt)
  7. // constantly be reading lines
  8. go func() {
  9. for {
  10. line, err := t.ReadLine()
  11. if err == io.EOF {
  12. log.Printf("got EOF")
  13. }
  14. if err != nil {
  15. log.Printf("got err")
  16. }
  17. if line == "" {
  18. continue
  19. }
  20. log.Printf("LINE: %s", line)
  21. }
  22. }()

huangapple
  • 本文由 发表于 2022年6月27日 06:26:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/72765557.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定