echo або print / dev / stdin / dev / stdout / dev / stderr


14

Я хочу надрукувати значення / dev / stdin, / dev / stdout та / dev / stderr.

Ось мій простий сценарій:

#!/bin/bash
echo your stdin is : $(</dev/stdin)
echo your stdout is : $(</dev/stdout)
echo your stderr is : $(</dev/stderr)

я використовую наступні труби:

[root@localhost home]# ls | ./myscript.sh
[root@localhost home]# testerr | ./myscript.sh

тільки $(</dev/stdin)здається, що це працює, я також виявив і деякі інші питання, якими користуються люди: "${1-/dev/stdin}"спробував це без успіху.

Відповіді:


30

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виводив те, що було прочитано після.


2

stdoutі stderrце виходи, ви не читаєте з них, ви можете лише писати їм. Наприклад:

echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr

програми записують у stdout за замовчуванням, тому перша еквівалентна

echo "this is stdout"

і ви можете перенаправляти stderr іншими способами, такими як

echo "this is stderr" 1>&2

1
У Linux echo xце не те саме, як echo x > /dev/stdoutякщо stdout не йде в трубу або деякі символьні пристрої, такі як tty-пристрій. Наприклад, якщо stdout переходить у звичайний файл, echo x > /dev/stdoutвін уріже файл і замінить його вміст x\nзамість запису x\nна поточній позиції stdout.
Стефан Шазелас

@ Michael-Daffin> Дякую, тож якщо я хочу прочитати stdout та stderr, я можу використовувати лише кота? (оскільки вони файли), наприклад, я хочу показати користувачеві останню помилку, що сталася echo this is your error $(cat /dev/stderr)?
Омар БІСТАМІ

@OmarBISTAMI Ви не можете читати з, stdoutі stderrвони є вихідними.
Kusalananda

1

myscript.sh:

#!/bin/bash
filan -s

Потім запустіть:

$ ./myscript.sh
    0 tty /dev/pts/1
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh
    0 pipe 
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh > out.txt
$ cat out.txt
    0 pipe 
    1 file /tmp/out.txt
    2 tty /dev/pts/1

Ви, ймовірно , необхідно встановити filanз sudo apt install socatпершим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.