stdin
, stdout
та stderr
є потоками, приєднаними до дескрипторів файлів 0, 1 і 2 відповідно процесу.
У запиті інтерактивної оболонки в емуляторі терміналу або терміналу всі ці 3 дескриптори файлів посилаються на той самий опис відкритого файлу, який був би отриманий, відкривши файл термінального або псевдотермінального пристрою (щось на зразок /dev/pts/0
) у read + write режим.
Якщо з цієї інтерактивної оболонки ви запустите свій скрипт, не використовуючи перенаправлення, ваш сценарій успадкує ці дескриптори файлів.
У 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
на порожній рядок. Потім вони передадуть розширення (те, що ви набрали позбавленим останніх рядків і підлягаєте спліт + glob), до echo
якого потім виведете його на stdout (для відображення).
Однак у:
echo $(</dev/stdout)
в bash
( і bash
тільки ), важливо усвідомити, що всередині $(...)
stdout був перенаправлений. Зараз це труба. У випадку з bash
дочірньою оболонкою процес зчитування вмісту файлу (тут /dev/stdout
) і записування його в трубу, в той час як батьківський зчитує з іншого кінця, щоб скласти розширення.
У цьому випадку, коли цей дочірній процес баш відкривається /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
Подивіться, як $(cat <&3)
прочитав те, що залишилося після першого printf
і зробив це, також перемістив позицію stdout повз нього, щоб наступний printf
виводив те, що було прочитано після.
echo x
це не те саме, якecho x > /dev/stdout
якщо stdout не йде в трубу або деякі символьні пристрої, такі як tty-пристрій. Наприклад, якщо stdout переходить у звичайний файл,echo x > /dev/stdout
він уріже файл і замінить його вмістx\n
замість записуx\n
на поточній позиції stdout.