New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: os: File.IsTerminal #28572

Open
neild opened this Issue Nov 3, 2018 · 7 comments

Comments

Projects
None yet
6 participants
@neild
Contributor

neild commented Nov 3, 2018

Add an IsTerminal method to os.File, equivalent to the Unix stdlib isatty():

// IsTerminal returns true if the file is associated with a terminal.
func (*File) IsTerminal() bool {}

The github.com/mattn/go-isatty package provides an equivalent function. It has 476 imports and 254 stars, which indicates that there is a fair degree of demand for this feature. I think the standard library should provide it.

@gopherbot gopherbot added this to the Proposal milestone Nov 3, 2018

@gopherbot gopherbot added the Proposal label Nov 3, 2018

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Nov 3, 2018

At one time we discussed having a golang.org/x/terminal package to provide some more-or-less generic terminal handling capabilities.

I wonder if os.IsTerminal would be somewhat frustrating in the absence of anything portable you could do with a terminal, such as turning off echo. What would this be used for?

@neild

This comment has been minimized.

Contributor

neild commented Nov 3, 2018

One thing I want it for is to let protoc-gen-go print an error if it's invoked interactively.

There's a progress bar library used fairly extensively inside Google which alters its output depending on whether it's writing to a terminal.

Looking through imports of the existing package would presumably find more uses.

A full terminal package would be nice, but I'd hate to make the perfect the enemy of the good. Plus, not all cases need a full terminal library.

@neild

This comment has been minimized.

Contributor

neild commented Nov 3, 2018

As for what you can do with just this function: \r can get you a surprising way. Assuming the terminal supports the usual ANSI escape sequences if TERM is set and isatty(stderr) is fairly reasonable these days. (And points at why you might want IsTerminal, because ANSI escapes are more portable than a isatty implementation.)

I think it might be possible to write a full curses library using only existing functions in the os package--with the exception of this one.

@mvdan

This comment has been minimized.

Member

mvdan commented Nov 3, 2018

I agree that just knowing if a file is a terminal can be useful on its own. I currently use https://godoc.org/golang.org/x/crypto/ssh/terminal#IsTerminal, like:

terminal.IsTerminal(int(f.Fd()))

If this is simple enough to add and maintain, my vote is in favor of adding this to the standard library.

@tklauser

This comment has been minimized.

Member

tklauser commented Nov 5, 2018

If this is simple enough to add and maintain, my vote is in favor of adding this to the standard library.

I think this should be simple enough to add by adding a copy of IoctlGetTermios from x/sys/unix to internal/syscall/unix and add/define the correct TCGET* value per GOOS/GOARCH for IoctlReadTermios. IsTerminal for all unix geese would then be:

import "internal/syscall/unix"

func (f *File) IsTerminal() bool {
        _, err := unix.IoctlGetTermios(f.Fd(), unix.IoctlReadTermios)
        return err == nil
} 

For windows everything that is needed is already in syscall, so IsTerminal would just be:

func (f *File) IsTerminal() bool {
        var st uint32
        err := syscall.GetConsoleMode(syscall.Handle(f.Fd()), &st)
        return err == nil
}  

I'm not sure about plan9. IsTerminal from x/crypto/ssh/terminal is currently:

func IsTerminal(fd int) bool {
        return false
}
@tklauser

This comment has been minimized.

Member

tklauser commented Nov 6, 2018

I'm not sure about plan9. IsTerminal from x/crypto/ssh/terminal is currently:

func IsTerminal(fd int) bool {
        return false
}

Looking at https://plan9.io/sources/plan9/sys/src/ape/lib/ap/plan9/isatty.c, I think the following might work for plan9:

func (f *File) IsTerminal() bool {
        return strings.HasSuffix(f.name, "/dev/cons") 
}
@rsc

This comment has been minimized.

Contributor

rsc commented Nov 28, 2018

One thing I want it for is to let protoc-gen-go print an error if it's invoked interactively.

Why not print the error always? The main objection I have to isatty is programs that change behavior based on whether they are run on a tty. If you think protoc-gen-go should be invoked with a pipe you could always check that (os.Stdin.Fstat).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment