sontags
sontags

Reputation: 3241

Check if there is something to read on STDIN in Golang

I need a command line utility to behave different if some string is piped into its STDIN. Here's some minimal example:

package main // file test.go

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    bytes, _ := ioutil.ReadAll(os.Stdin)

    if len(bytes) > 0 {
        fmt.Println("Something on STDIN: " + string(bytes))
    } else {
        fmt.Println("Nothing on STDIN")
    }
}

This works fine if you call it like that:

echo foo | go run test.go

If test.go is called without anything on STDIN, the thing stucks at...

bytes, _ := ioutil.ReadAll(os.Stdin)

... waiting for EOF.

What do I need to do to get this going?

Thanks in advance!

Upvotes: 45

Views: 18077

Answers (5)

thepudds
thepudds

Reputation: 5284

The other answers here did not seem to work under Git Bash on Windows, which is the default bash for GitHub Actions on Windows. isTerminal from golang.org/x/term also did not seem to work.

For me, using https://github.com/mattn/go-isatty seems to work across platforms, including Git Bash / cygwin / MinGW on Windows.

Example usage (playground link):

func main() {
    if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) {
        fmt.Println("stdin is terminal, nothing on os.Stdin")
        return
    }

    // stdin might or might not have data, but given it is not a terminal,
    // we should be able to read from it.
    //
    // Some examples:
    //    echo foo | go run .     # we can read "foo" from os.Stdin
    //    go run . < /dev/null    # we can read os.Stdin, but it is empty
    fmt.Println("stdin is not a terminal and we can read it")
    io.Copy(os.Stdout, os.Stdin)
}

Note that this is answering "Can I read from os.Stdin (which might be zero length)?", and not "Is there non-empty data in os.Stdin?". At least for my use case, the first form of the question is useful where "zero length data" is a type of input data that is OK to read.

I would be curious to hear if it does not work for someone else (including what platform).

Upvotes: 1

Nick Craig-Wood
Nick Craig-Wood

Reputation: 54081

Use the IsTerminal function from code.google.com/p/go.crypto/ssh/terminal (which was exp/terminal) or the Isatty function from github.com/andrew-d/go-termutil which is a much more focussed package.

If stdin is a terminal/tty then you aren't being piped stuff and you can do something different.

Here is an example

package main

import (
    "fmt"
    termutil "github.com/andrew-d/go-termutil"
    "io"
    "os"
)

func main() {
    if termutil.Isatty(os.Stdin.Fd()) {
        fmt.Println("Nothing on STDIN")
    } else {
        fmt.Println("Something on STDIN")
        io.Copy(os.Stdout, os.Stdin)
    }
}

Testing

$ ./isatty 
Nothing on STDIN
$ echo "hello" | ./isatty 
Something on STDIN
hello
$ (sleep 1 ; echo "hello") | ./isatty 
Something on STDIN
hello

Upvotes: 21

bullgare
bullgare

Reputation: 1763

If none of the above works for you, try this way:

stat, err := os.Stdin.Stat()
if err != nil {
    return nil, fmt.Errorf("you have an error in stdin:%s", err)
}
if (stat.Mode() & os.ModeNamedPipe) == 0 {
    return nil, errors.New("you should pass smth to stdin")
}

It worked for me in both darwin (Mac OS) and linux (Ubuntu).

Upvotes: 3

ostler.c
ostler.c

Reputation: 3472

I solved this by using os.ModeCharDevice:

stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
    fmt.Println("data is being piped to stdin")
} else {
    fmt.Println("stdin is from a terminal")
}

Upvotes: 69

sontags
sontags

Reputation: 3241

This does it:

package main // file test.go

import (
    "bufio"
"fmt"
    "os"
)

func main() {
    in := bufio.NewReader(os.Stdin)
    stats, err := os.Stdin.Stat()
    if err != nil {
        fmt.Println("file.Stat()", err)
    }

    if stats.Size() > 0 {
        in, _, err := in.ReadLine()
        if err != nil {
            fmt.Println("reader.ReadLine()", err)
        }
        fmt.Println("Something on STDIN: " + string(in))
    } else {
        fmt.Println("Nothing on STDIN")
    }
}

Thanks @Kluyg !

Upvotes: 1

Related Questions