【问题标题】:Get terminal size in Go在 Go 中获取终端大小
【发布时间】:2013-05-10 06:48:36
【问题描述】:

如何使用 Golang 获取 tty 大小?我正在尝试通过执行stty size 命令来做到这一点,但我无法正确编写代码。

package main

import (
  "os/exec"
  "fmt"
  "log"
)

func main() {
  out, err := exec.Command("stty", "size").Output()
  fmt.Printf("out: %#v\n", out)
  fmt.Printf("err: %#v\n", err)
  if err != nil {
    log.Fatal(err)
  }
}

输出:

out: []byte{}
err: &exec.ExitError{ProcessState:(*os.ProcessState)(0xc200066520)}
2013/05/16 02:35:57 exit status 1
exit status 1

我认为这是因为 Go 生成了一个与当前 tty 无关的进程,它正在使用它。如何将命令与当前终端关联以获取其大小?

【问题讨论】:

    标签: go tty


    【解决方案1】:

    我只是想添加一个新答案,因为我最近遇到了这个问题。官方 ssh 包https://godoc.org/golang.org/x/crypto/ssh/terminal 中有一个终端包。

    这个包提供了一种轻松获取终端大小的方法。

    width, height, err := terminal.GetSize(0)

    0 将是您想要大小的终端的文件描述符。要获取 fd 或您当前的终端,您可以随时使用 int(os.Stdin.Fd())

    在幕后,它使用系统调用来获取给定 fd 的终端大小。

    【讨论】:

    • 如果os.Stdin 没有连接到终端,例如从管道读取,您可以使用int(os.Stdout.Fd()) 来查找尺寸。
    • 不幸的是,它在 Windows 上对我不起作用。无论我做什么,它都会向我发送错误The handle is invalid.
    • 对我来说,int(os.Stdin.Fd()) 不工作但int(os.Stdout.Fd()) 工作正常。 - 在 WSL2、Ubuntu 20.04、VSCode 上工作,使用命令 $ go test -run TestTerminalSize
    【解决方案2】:

    我遇到了类似的问题。这是我最终得到的结果。

    它不使用子进程,因此在某些情况下可能是可取的。

    import (
        "syscall"
        "unsafe"
    )
    
    type winsize struct {
        Row    uint16
        Col    uint16
        Xpixel uint16
        Ypixel uint16
    }
    
    func getWidth() uint {
        ws := &winsize{}
        retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
            uintptr(syscall.Stdin),
            uintptr(syscall.TIOCGWINSZ),
            uintptr(unsafe.Pointer(ws)))
    
        if int(retCode) == -1 {
            panic(errno)
        }
        return uint(ws.Col)
    }
    

    【讨论】:

    【解决方案3】:

    如果你让子进程访问父进程的stdin,它会起作用:

    package main
    
    import (
      "os/exec"
      "fmt"
      "log"
      "os"
    )
    
    func main() {
      cmd := exec.Command("stty", "size")
      cmd.Stdin = os.Stdin
      out, err := cmd.Output()
      fmt.Printf("out: %#v\n", string(out))
      fmt.Printf("err: %#v\n", err)
      if err != nil {
        log.Fatal(err)
      }
    }
    

    产量:

    out: "36 118\n"
    err: <nil>
    

    【讨论】:

    • 你可以尝试将这些行放到一个函数中,并通过testing模块设置一个单元测试来测试这个函数,你会发现你总是得到空数组。
    • 我已经尝试过了,但正如@Kingname 在测试时所说的那样,我不断得到一个空数组。
    【解决方案4】:

    你可以使用golang.org/x/term包(https://pkg.go.dev/golang.org/x/term

    例子

    package main
    
    import "golang.org/x/term"
    
    func main() {
        if term.IsTerminal(0) {
            println("in a term")
        } else {
            println("not in a term")
        }
        width, height, err := term.GetSize(0)
        if err != nil {
            return
        }
        println("width:", width, "height:", height)
    }
    

    输出

    in a term
    width: 228 height: 27
    

    【讨论】:

    • 谢谢!这个结果的单位是什么? cmpx 或其他?
    • @JsonBruce 行和列。
    【解决方案5】:

    如果有人感兴趣,我制作了一个包以使这更容易。

    https://github.com/wayneashleyberry/terminal-dimensions

    package main
    
    import (
        "fmt"
    
        terminal "github.com/wayneashleyberry/terminal-dimensions"
    )
    
    func main() {
        x, _ := terminal.Width()
        y, _ := terminal.Height()
        fmt.Printf("Terminal is %d wide and %d high", x, y)
    }
    

    【讨论】:

    • 不支持windows,这使得包对跨平台开发毫无用处。
    【解决方案6】:

    由于这里还没有其他人提出可以在 WindowsUnix 上运行的跨平台解决方案,所以我继续整理了一个支持两者都有。

    https://github.com/nathan-fiscaletti/consolesize-go

    package main
    
    import (
        "fmt"
    
        "github.com/nathan-fiscaletti/consolesize-go"
    )
    
    func main() {
        cols, rows := consolesize.GetConsoleSize()
        fmt.Printf("Rows: %v, Cols: %v\n", rows, cols)
    }
    

    【讨论】:

    • 我想确认它确实可以在 Windows 上运行。我尝试了不同的场景,包括异国情调的场景,比如打开 vim 和拆分窗口以获得不同大小的终端(在另一个终端内,运行 vim)。有用。我确实有好几次尺寸不正确,但还没有准备好报告,因为我不确定是库错误还是我草率的测试。
    • @shytikov 如果该解决方案适合您,请考虑对答案进行投票:)
    • 是的,绝对的!
    【解决方案7】:

    我有一个使用 tcell 模块的实现,在底层它仍将使用基于调用本机 dlls 的方法,但如果您正在搜索终端尺寸,那么您很有可能需要它无论如何都要打包:

    package main
    
    import (
        "fmt"
        "github.com/gdamore/tcell"
    )
    
    func main() {
        screen, _ := tcell.NewScreen()
        screen.Init()
        
        w, h := screen.Size()
        fmt.Println(w, h)
    }
    

    【讨论】:

    • 在我的用例中,我只需要大小,这使得正常的 fmt.PrintX 行为异常
    猜你喜欢
    • 1970-01-01
    • 2011-10-12
    • 2021-01-12
    • 2012-04-05
    • 2020-10-10
    • 2014-06-15
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    相关资源
    最近更新 更多