echo или print / dev / stdin / dev / stdout / dev / stderr

13

Я хочу напечатать значение / 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}"пытался безуспешно.

Омар БИСТАМИ
источник

Ответы:

29

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

Стефан Шазелас
источник
2

stdoutи stderrявляются выходами, вы не читаете из них, вы можете только написать им. Например:

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

программы пишут в стандартный вывод по умолчанию, поэтому первый эквивалентен

echo "this is stdout"

и вы можете перенаправить stderr другими способами, такими как

echo "this is stderr" 1>&2
Майкл Даффин
источник
1
В Linux echo xэто не то же самое, что echo x > /dev/stdoutесли stdout не переходит на канал или некоторые символьные устройства, такие как tty-устройство. Например, если стандартный вывод переходит в обычный файл, echo x > /dev/stdoutон усекает файл и заменяет его содержимое x\nвместо записи x\nв текущей позиции стандартного вывода.
Стефан
@ Michael-Daffin> Спасибо, поэтому, если я хочу прочитать stdout и stderr, я могу использовать только cat? (так как они файлы), например, я хочу показать пользователю последнюю ошибку, которая произошла echo this is your error $(cat /dev/stderr)?
Омар БИСТАМИ
@ OmarBISTAMI Вы не можете читать, stdoutи stderrони являются выходами.
Кусалананда
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первым.

wisbucky
источник