I would like to read from the original stdin of a Go program. For example, if I did echo test stdin | go run test.go, I would want to have access to “test stdin”. I’ve tried reading from os.Stdin, but if there’s nothing in it, then it will wait for input. I also tried checking the size first, but the os.Stdin.Stat().Size() is 0 even when input is passed in.
What can I do?
I think your question per se has no sensible answer because there’s just no such thing as "initial stdin". Unix-like OSs, and Windows implement the concept of "standard streams", which works like this (simplified): when a process is created, it automagically has three file descriptors (handles in Windows) open — stdin, stdout and stderr. No doubts, you’re familiar with this concept, but I’d like to stress the meaning of the word "stream" there — in your example, when you call
the shell creates a pipe, spawns two processes (one for
echoand one for your binary) and makes use of the pipe it created: the pipe’s write FD is attached to theecho‘s stdout and the pipe’s read FD is attached to your binary’s stdin. Then whatever theechoprocess pleases to write to its stdout is piped (sic!) to the stdin of your process.(In reality most today’s shells implement
echoas a built-in primitive but this does not in any way change the semantics; your could as well have tried/bin/echoinstead, which is a real program. Also note that I just used./stdinto refer to your program — this is for clarity, asgo run stdin.gowould do exactly this, in the end.)Note several crucial things here:
echoin your case) is not oblidged to write anything to its stdout (for instance,echo -nwould not write anything to its stdout and exit successfully).readis attempted, which fails).Let’s wrap this up: the behaviour you’re observing is correct and normal. If you expect to get any data from stdin, you must not expect it to be readily available. If you also don’t want to block on stdin, then create a goroutine which would do blocking reads from stdin in an endless loop (but checking for the EOF condition) and pass collected data up over a channel (possibly after certain processing, if needed).
1 This is why certain tools which usually occur between two pipes in a pipeline, such as
grep, might have special options to make them flush their stdout after writing each line — read about the--line-bufferedoption in thegrepmanual page for one example. People who are not aware of this "full buffering by default" semantics are puzzled whytail -f /path/to/some/file.log | grep whatever | sed ...seems to stall and not display anything when it’s obvious the monitored file gets updated.As a side note: if you were to run your binary "as is", like in
that would not meant the spawned process would not have stdin (or "initial stdin" or whaveter), instead, its stdin would be connected to the same stream your shell receives your keyboard input from (so you could directly type something to your process’s stdin).
The only sure way to have a process’s stdin connected to nowhere is to use
on Unix-like OSes and
on Windows. This "null device" makes the process see EOF on the first
readfrom its stdin.