stdin
, stdout
и stderr
являются потоками, прикрепленными к файловым дескрипторам 0, 1 и 2 соответственно процесса.
По приглашению интерактивной оболочки в терминале или эмуляторе терминала все эти 3 файловых дескриптора будут ссылаться на одно и то же описание открытого файла, которое было бы получено при открытии файла терминала или псевдотерминального устройства (что-то вроде /dev/pts/0
) в режиме чтения + записи. Режим.
Если из этой интерактивной оболочки вы запустите свой скрипт без использования перенаправления, ваш скрипт унаследует эти файловые дескрипторы.
В Linux /dev/stdin
, /dev/stdout
, /dev/stderr
символические ссылки /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
соответственно, сами специальные символические ссылки на реальный файл , который открыт на этих файловых дескрипторов.
Это не stdin, stdout, stderr, это специальные файлы, которые определяют, к каким файлам идут stdin, stdout, stderr (обратите внимание, что в других системах, отличных от Linux, у которых есть эти специальные файлы), они отличаются.
чтение чего-либо из stdin означает чтение из дескриптора файла 0 (который будет указывать где-то в файле, на который ссылается /dev/stdin
).
Но в $(</dev/stdin)
этом случае оболочка не читает из stdin, она открывает новый файловый дескриптор для чтения в том же файле, что и тот, что открыт в stdin (то есть чтение в начале файла, а не в том месте, на которое в данный момент указывает stdin).
За исключением специального случая, когда терминальные устройства открываются в режиме чтения + записи, stdout и stderr обычно не открыты для чтения. Они предназначены для потоков, в которые вы пишете . Таким образом, чтение из файлового дескриптора 1 обычно не будет работать. В Linux, открытие /dev/stdout
или /dev/stderr
чтение (как в $(</dev/stdout)
) будет работать и позволит вам читать из файла, куда идет stdout (и если stdout был канал, который будет читать с другого конца канала, и если это был сокет , он потерпит неудачу, так как вы не можете открыть сокет).
В нашем случае сценарий запускается без перенаправления по приглашению интерактивной оболочки в терминале, все файлы / dev / stdin, / dev / stdout и / dev / stderr будут представлять собой файл устройства терминала / dev / pts / x.
Чтение из этих специальных файлов возвращает то, что отправлено терминалом (что вы набираете на клавиатуре). Запись на них отправит текст в терминал (для отображения).
echo $(</dev/stdin)
echo $(</dev/stderr)
будет таким же. Чтобы развернуть $(</dev/stdin)
, оболочка откроет этот / dev / pts / 0 и будет читать то, что вы вводите, пока вы не нажмете ^D
на пустую строку. Затем они передадут расширение (то, что вы набрали, удаляя завершающие символы новой строки и подлежащие split + glob), echo
которое затем выведет его на стандартный вывод (для отображения).
Однако в:
echo $(</dev/stdout)
в bash
( и bash
только ) важно понимать, что внутри $(...)
stdout был перенаправлен. Теперь это труба. В случае bash
дочернего процесса оболочки считывает содержимое файла (здесь /dev/stdout
) и записывает его в канал, в то время как родительский читает с другого конца, чтобы составить расширение.
В этом случае, когда этот дочерний процесс bash открывается /dev/stdout
, он фактически открывает конец чтения канала. Ничего из этого не выйдет, это тупиковая ситуация.
Если вы хотите прочитать из файла, на который указывает сценарий stdout, вы можете обойти его:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Это дублирует fd 1 на fd 3, поэтому / dev / fd / 3 будет указывать на тот же файл, что и / dev / stdout.
С помощью сценария, как:
#! /bin/bash -
printf 'content of file on stdin: %s\n' "$(</dev/stdin)"
{ printf 'content of file on stdout: %s\n' "$(</dev/fd/3)"; } 3<&1
printf 'content of file on stderr: %s\n' "$(</dev/stderr)"
Когда запускается как:
echo bar > err
echo foo | myscript > out 2>> err
Вы увидите out
позже:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Если в отличие от чтения /dev/stdin
, /dev/stdout
, /dev/stderr
, вы хотите , чтобы читать из стандартного ввода, стандартный вывод и стандартный поток ошибок (что бы еще меньше смысла), вы могли бы сделать:
#! /bin/sh -
printf 'what I read from stdin: %s\n' "$(cat)"
{ printf 'what I read from stdout: %s\n' "$(cat <&3)"; } 3<&1
printf 'what I read from stderr: %s\n' "$(cat <&2)"
Если вы снова запустили этот второй скрипт как:
echo bar > err
echo foo | myscript > out 2>> err
Вы увидите в out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
и в err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Для stdout и stderr, cat
терпит неудачу, потому что файловые дескрипторы были открыты только для записи , а не для чтения, расширения $(cat <&3)
и $(cat <&2)
пусто.
Если вы назвали это как:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(где <>
открывается в режиме чтения + записи без усечения), вы увидите в out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
и в err
:
err
Вы заметите, что из stdout ничего не читалось, потому что предыдущий printf
переписал содержимое out
с what I read from stdin: foo\n
и оставил позицию stdout в этом файле сразу после. Если вы заполнили out
более крупный текст, например:
echo 'This is longer than "what I read from stdin": foo' > out
Тогда вы получите out
:
what I read from stdin: foo
read from stdin": foo
what I read from stdout: read from stdin": foo
what I read from stderr: err
Посмотрите, как the $(cat <&3)
прочитал то, что осталось после первого, printf
и, таким образом, переместил позицию stdout за ним так, чтобы следующий printf
выводил то, что было прочитано после.
echo x
это не то же самое, чтоecho x > /dev/stdout
если stdout не переходит на канал или некоторые символьные устройства, такие как tty-устройство. Например, если стандартный вывод переходит в обычный файл,echo x > /dev/stdout
он усекает файл и заменяет его содержимоеx\n
вместо записиx\n
в текущей позиции стандартного вывода.