Ответ 1
Я думаю, что ваш вопрос сам по себе не имеет разумного ответа, потому что просто нет такого понятия, как "начальный stdin". Unix-подобные ОС и Windows реализуют концепцию "стандартные потоки" , которая работает так (упрощена): когда процесс создается, он Automagically имеет три файловых дескриптора (дескрипторы в Windows) open — stdin, stdout и stderr. Не сомневайтесь, вы знакомы с этой концепцией, но я хотел бы подчеркнуть значение слова "поток" там:— в вашем примере, когда вы вызываете
$ echo 'test stdin' | ./stdin
оболочка создает pipe, генерирует два процесса (один для echo
и один для вашего двоичного кода) и использует созданная труба: труба write FD присоединена к выходу echo
, а труба, считываемая FD, прикреплена к вашему двоичному stdin. Тогда любой процесс echo
, который нравится писать в его stdout, передается по каналам (sic!) В stdin вашего процесса.
(На самом деле большинство современных оболочек реализуют echo
как встроенный примитив, но это никоим образом не изменяет семантику: вместо этого вы могли бы попробовать /bin/echo
, что является реальной программой. Также обратите внимание, что я просто использовал ./stdin
, чтобы обратиться к вашей программе - это для ясности, поскольку go run stdin.go
сделает именно это, в конце.)
Обратите внимание на несколько важных вещей здесь:
- Процесс записи (
echo
в вашем случае) не закрывается, чтобы записать что-либо в его stdout (например,echo -n
ничего не записывал бы в его stdout и не удалялся успешно). - Он также может делать произвольные задержки, записывая свои данные (либо потому, что он хочет сделать такие задержки, либо потому, что он был выгружен ОС, либо спит в каком-то syscall, ожидающем некоторых загруженных системных ресурсов и т.д.).
- Буферы ОС переносятся по трубам. Это означает, что процесс записи отправляется в канал, может появляться в произвольных фрагментах на стороне чтения. 1
- Есть только два способа узнать, что на стороне записи больше нет данных для отправки по каналу:
- Как-то кодировать это в самих данных (это означает использование согласованного протокола передачи данных между автором и читателем).
- Писатель может закрыть свою сторону трубы, что приведет к условию "конец файла" на стороне читателя (но только после того, как буфер будет разряжен, а один другой вызов
read
будет выполнен, что не удастся).
Позвольте обернуть это: поведение, которое вы наблюдаете, является правильным и нормальным. Если вы ожидаете получить какие-либо данные из stdin, вы не должны ожидать, что он будет легко доступен. Если вы также не хотите блокировать stdin, тогда создайте goroutine, который будет блокировать чтение из stdin в бесконечном цикле (но проверять условие EOF) и передавать собранные данные по каналу (возможно, после определенной обработки, если необходимо).
1 Вот почему некоторые инструменты, которые обычно встречаются между двумя трубами в конвейере, например grep
, могут иметь специальные опции, чтобы заставить их сбросить их stdout после записи каждой строки — прочтите пример --line-buffered
на странице grep
руководства для примера. Люди, которые не знают эту семантику "полной буферизации по умолчанию", недоумевают, почему tail -f /path/to/some/file.log | grep whatever | sed ...
, похоже, заглох и не отображает ничего, когда очевидно, что проверенный файл обновляется.
В качестве побочного примечания: если вы должны были запустить свой двоичный "как есть", как в
$ ./stdin
это не означало бы, что порожденный процесс не будет иметь stdin (или "начальный stdin" или whaveter), вместо этого его stdin будет подключен к тому же потоку, который ваша оболочка получает от вашего импорта с клавиатуры (так что вы можете прямо набрать что-то ваш процесс stdin).
Единственный верный способ связать процесс stdin с нигде - использовать
$ ./stdin </dev/null
в Unix-подобных ОС и
C:\> stdin <NUL
в Windows. Этот "null device" заставляет процесс видеть EOF на первом read
из его stdin.