How to get tty size with Golang? I am trying do this with executing stty size command, but I can't craft code right.
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)
}
}
Output:
out: []byte{}
err: &exec.ExitError{ProcessState:(*os.ProcessState)(0xc200066520)}
2013/05/16 02:35:57 exit status 1
exit status 1
I think this is because Go spawns a process not related to the current tty, with which it is working. How can I relate the command to current terminal in order to get its size?
I just wanted to add a new answer since I ran into this problem recently. There is a terminal package which lives inside the official ssh package https://godoc.org/golang.org/x/crypto/ssh/terminal.
This package provides a method to easily get the size of a terminal.
width, height, err := terminal.GetSize(0)
0 would be the file descriptor of the terminal you want the size of. To get the fd or you current terminal you can always do int(os.Stdin.Fd())
Under the covers it uses a syscall to get the terminal size for the given fd.
I was stuck on a similar problem. Here is what I ended up with.
It doesn't use a subprocess, so might be desirable in some situations.
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)
}
It works if you give the child process access to the parent's 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)
}
}
Yields:
out: "36 118\n"
err: <nil>
You can use golang.org/x/term package (https://pkg.go.dev/golang.org/x/term)
Example
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)
}
Output
in a term
width: 228 height: 27
Since no one else here has yet to present a cross-platform solution that will work on both Windows and Unix, I went ahead and put together a library that supports both.
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)
}
If anyone's interested I made a package to make this easier.
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)
}
I have one implementation that uses tcell module, under the hood it will still use approach that based on calling native dlls, but if you're searching for terminal dimensions there is a great chance that you would need that package anyway:
package main
import (
"fmt"
"github.com/gdamore/tcell"
)
func main() {
screen, _ := tcell.NewScreen()
screen.Init()
w, h := screen.Size()
fmt.Println(w, h)
}
Related
I have a somewhat challenging situation where I need to write into a system command stdin the same stdout it outputs (in another running program), here's an example program that represents what I mean:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
)
func main() {
rand.Seed(time.Now().Unix())
var greetings []string = []string{"hi", "hola", "bonjour", "hallo", "whats up"}
var greeting string = greetings[rand.Intn(len(greetings))]
fmt.Println(greeting)
reader := bufio.NewReader(os.Stdin)
message, _ := reader.ReadString('\n')
if message == greeting+"\n" {
fmt.Println("nice to meet you!")
} else {
fmt.Println("oops!")
}
}
Since you greet with a random greeting, you have to read the stdout, send it to stdin and also capture if it was the correct answer or not. I've tried with stdinpipes but it freezes waiting for the stdin close since I think that only works for the start of the command run only, so for a running program it hasn't been working for me...
I appreciate any help!
EDIT
I wanted to add sort of what I was trying to do, I've tried without channels as well but it didn't seem to make a difference on the outcome, it just freezes waiting for stdin to close and I need to get first stdout before closing stdin since it consists of it:
package main
import (
"io"
"os/exec"
)
func main() {
cmd := exec.Command("./executable_program")
stdout, _ := cmd.StdoutPipe()
stdin, _ := cmd.StdinPipe()
var c chan []byte = make(chan []byte)
cmd.Start()
go func() {
b, _ := io.ReadAll(stdout)
c <- b
}()
stdin.Write(<-c)
stdin.Close()
cmd.Wait()
}
You can use a pipe to join the stdout to the stdin of the program that you execute:
package main
import (
"io"
"os/exec"
)
func main() {
r, w := io.Pipe()
cmd := exec.Command("<name-of-program-to-run>")
cmd.Stdin = r
cmd.Stdout = w
cmd.Run()
}
To see this in action, first let's prepare a test program to be executed by the program above. This test program simply prints a line to stdout, and then reads each line of stdin and prints it to stdout until stdin is closed.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Fprint(os.Stdout, "priming the pump!\n")
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
line := s.Text()
fmt.Fprint(os.Stdout, line+"\n")
}
}
Then, we modify our initial program to print the bytes traversing through the pipe so we see what's going on.
package main
import (
"fmt"
"io"
"os/exec"
)
func main() {
r, w := io.Pipe()
sr := &readSpy{r: r}
wr := &writeSpy{w: w}
cmd := exec.Command("./test-program")
cmd.Stdin = sr
cmd.Stdout = wr
cmd.Run()
}
type readSpy struct {
r io.Reader
}
func (s *readSpy) Read(d []byte) (int, error) {
size, err := s.r.Read(d)
fmt.Println("readSpy read", string(d[:size]))
return size, err
}
type writeSpy struct {
w io.Writer
}
func (s *writeSpy) Write(d []byte) (int, error) {
size, err := s.w.Write(d)
fmt.Println("writeSpy wrote", string(d[:size]))
return size, err
}
Running the above, you will see the following getting printed in a infinite loop, which makes sense since the priming the pump! string is printed to stdout and fed right back to the stdin of the test program:
writeSpy wrote priming the pump!
readSpy read priming the pump!
...repeated forever...
My code is as follows. It is a simple echo program
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
mustCopy(os.Stdout, os.Stdin)
}
func mustCopy(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
fmt.Println(err)
if err != nil {
log.Fatal(err)
}
}
I also checked keyboard mappings for EOF on mac by using stty all it gives ^D as eof
Output of program is as follows
a
b
b
c
c
<nil>
As per the docs golang.org/pkg/io/#Copy and pointed by #BrunoReis Copy is behaving as expected as the err will be nil if Copy is terminated due to EOF
In the following, we replace io.Copy with io.CopyN to set expectation higher and close early.
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
mustCopy(os.Stdout, os.Stdin)
}
func mustCopy(dst io.Writer, src io.Reader) {
_, err := io.CopyN(dst, src, 2000) // 2000 is arbitrary, just big enough
fmt.Println(err)
if err != nil {
log.Fatal(err)
}
}
Example run:
a
a
b
b
c
c
EOF
2020/05/26 02:26:04 EOF
exit status 1
Is there an easy way to check the size of a Golang project? It's not an executable, it's a package that I'm importing in my own project.
You can see how big the library binaries are by looking in the $GOPATH/pkg directory (if $GOPATH is not exported go defaults to $HOME/go).
So to check the size of some of the gorilla http pkgs. Install them first:
$ go get -u github.com/gorilla/mux
$ go get -u github.com/gorilla/securecookie
$ go get -u github.com/gorilla/sessions
The KB binary sizes on my 64-bit MacOS (darwin_amd64):
$ cd $GOPATH/pkg/darwin_amd64/github.com/gorilla/
$ du -k *
284 mux.a
128 securecookie.a
128 sessions.a
EDIT:
Library (package) size is one thing, but how much space that takes up in your executable after the link stage can vary wildly. This is because packages have their own dependencies and with that comes extra baggage, but that baggage may be shared by other packages you import.
An example demonstrates this best:
empty.go:
package main
func main() {}
http.go:
package main
import "net/http"
var _ = http.Serve
func main() {}
mux.go:
package main
import "github.com/gorilla/mux"
var _ = mux.NewRouter
func main() {}
All 3 programs are functionally identical - executing zero user code - but their dependencies differ. The resulting binary sizes in KB:
$ du -k *
1028 empty
5812 http
5832 mux
What does this tell us? The core go pkg net/http adds significant size to our executable. The mux pkg is not large by itself, but it has an import dependency on net/http pkg - hence the significant file size for it too. Yet the delta between mux and http is only 20KB, whereas the listed file size of the mux.a library is 284KB. So we can't simply add the library pkg sizes to determine their true footprint.
Conclusion:
The go linker will strip out a lot of baggage from individual libraries during the build process, but in order to get a true sense of how much extra weight importing certain packages, one has to look at all of the pkg's sub-dependencies as well.
Here is another solution that makes use of https://pkg.go.dev/golang.org/x/tools/go/packages
I took the example provided by the author, and slightly updated it with the demonstration binary available here.
package main
import (
"flag"
"fmt"
"log"
"os"
"sort"
"golang.org/x/tools/go/packages"
)
func main() {
flag.Parse()
// Many tools pass their command-line arguments (after any flags)
// uninterpreted to packages.Load so that it can interpret them
// according to the conventions of the underlying build system.
cfg := &packages.Config{Mode: packages.NeedFiles |
packages.NeedSyntax |
packages.NeedImports,
}
pkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
os.Exit(1)
}
// Print the names of the source files
// for each package listed on the command line.
var size int64
for _, pkg := range pkgs {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v is %v b\n", pkgs[0].ID, size)
size = 0
for _, pkg := range allPkgs(pkgs) {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v and deps is %v b\n", pkgs[0].ID, size)
}
func allPkgs(lpkgs []*packages.Package) []*packages.Package {
var all []*packages.Package // postorder
seen := make(map[*packages.Package]bool)
var visit func(*packages.Package)
visit = func(lpkg *packages.Package) {
if !seen[lpkg] {
seen[lpkg] = true
// visit imports
var importPaths []string
for path := range lpkg.Imports {
importPaths = append(importPaths, path)
}
sort.Strings(importPaths) // for determinism
for _, path := range importPaths {
visit(lpkg.Imports[path])
}
all = append(all, lpkg)
}
}
for _, lpkg := range lpkgs {
visit(lpkg)
}
return all
}
You can download all the imported modules with go mod vendor, then count the lines of all the .go files that aren't test files:
package main
import (
"bytes"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
)
func count(mod string) int {
imp := fmt.Sprintf("package main\nimport _ %q", mod)
os.WriteFile("size.go", []byte(imp), os.ModePerm)
exec.Command("go", "mod", "init", "size").Run()
exec.Command("go", "mod", "vendor").Run()
var count int
filepath.WalkDir("vendor", func(s string, d fs.DirEntry, err error) error {
if strings.HasSuffix(s, ".go") && !strings.HasSuffix(s, "_test.go") {
data, err := os.ReadFile(s)
if err != nil {
return err
}
count += bytes.Count(data, []byte{'\n'})
}
return nil
})
return count
}
func main() {
println(count("github.com/klauspost/compress/zstd"))
}
I started to do programming contests in go (just to learn the language) and to my surprise found that
var T int
fmt.Scanf("%d", &T)
is unimaginably slow. How slow? To read 10^5 integers it take me 2.5 seconds (in comparison python does it in 0.8 secs).
So why is it so slow and how should I properly read int, uint64 and float64?
If you have only the integer as input, this should be faster (not tested though)
package main
import (
"io/ioutil"
"log"
"os"
"strconv"
)
func read() (int64, error) {
b, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return 0, err
}
// use strconv.ParseUint and strconv.ParseFloat in a similar way
return strconv.ParseInt(string(b[:len(b)-1]), 10, 0)
}
func main() {
i, err := read()
if err != nil {
log.Fatal(err)
}
println(i)
}
run it like this
echo 123 | go run main.go
for interactive input, you might want to use bufio.NewReader, see How to read input from console line?
Is there a way to scan a big.Int directly from the standard input in Go? Right now I'm doing this:
package main
import (
"fmt"
"math/big"
)
func main() {
w := new(big.Int)
var s string
fmt.Scan(&s)
fmt.Sscan(s, w)
fmt.Println(w)
}
I also could have used .SetString. But, is there a way to Scan the big.Int directly from the standard input without scanning a string or an integer first?
For example,
package main
import (
"fmt"
"math/big"
)
func main() {
w := new(big.Int)
n, err := fmt.Scan(w)
fmt.Println(n, err)
fmt.Println(w.String())
}
Input (stdin):
295147905179352825857
Output (stdout):
1 <nil>
295147905179352825857
As far as I know - no, there's no other way. In fact, what you've got is the default example they have for scanning big.Int in the documentation.
package main
import (
"fmt"
"log"
"math/big"
)
func main() {
// The Scan function is rarely used directly;
// the fmt package recognizes it as an implementation of fmt.Scanner.
i := new(big.Int)
_, err := fmt.Sscan("18446744073709551617", i)
if err != nil {
log.Println("error scanning value:", err)
} else {
fmt.Println(i)
}
}
You can see the relevant section here - http://golang.org/pkg/math/big/#Int.Scan