在 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
【讨论】:
谢谢!这个结果的单位是什么?cm
或 px
或其他?
@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】:由于这里还没有其他人提出可以在 Windows 和 Unix 上运行的跨平台解决方案,所以我继续整理了一个支持两者都有。
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 中获取终端大小的主要内容,如果未能解决你的问题,请参考以下文章