在 Go 中获取终端大小

Posted

技术标签:

【中文标题】在 Go 中获取终端大小【英文标题】:Get terminal size in 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.ExitErrorProcessState:(*os.ProcessState)(0xc200066520)
2013/05/16 02:35:57 exit status 1
exit status 1

我认为这是因为 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)

【讨论】:

不使用“不安全”包的另一种选择:github.com/golang/crypto/blob/… 不幸的是,该替代方案最终仍然在引擎盖下使用 unsafe。 github.com/golang/sys/blob/… @Strom:在我看来,它是一个不安全函数的安全包装器。有点像 Rust :)【参考方案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 行为异常

以上是关于在 Go 中获取终端大小的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Go 中获取变量的内存大小?

获取终端窗口的大小(行/列)

在 c 中为 Windows 获取终端大小?

c_cpp 通过IOCTL获取终端大小

原创go语言学习(十四)IO操作1

Go语言中的fmt.Scan使用说明