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в текущей позиции стандартного вывода.