stdin
,stdout
和stderr
是分别附加到进程的文件描述符 0、1和2的流。
在终端或终端仿真器中的交互式外壳程序提示下,所有这三个文件描述符都将引用相同的打开文件描述,该描述可以通过/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)
,shell没有从stdin读取数据,而是打开了一个新文件描述符,用于读取与在stdin上打开的文件相同的文件(因此从文件的开头进行读取,而不是stdin当前指向的位置)。
除了在以读写模式打开的终端设备的特殊情况下,stdout和stderr通常不会打开以进行读取。它们注定是您要写入的流。因此,从文件描述符1读取通常将不起作用。在Linux上,打开/dev/stdout
或/dev/stderr
进行读取(如中所述$(</dev/stdout)
)将起作用,并且可以让您从stdout所在的文件中读取(如果stdout是管道,则将从管道的另一端读取,并且如果它是套接字) ,否则将失败,因为您无法打开套接字)。
在我们的脚本在终端中出现交互式shell的提示下运行而无需重定向的情况下,所有/ 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
仅)中,重要的是要意识到inside $(...)
,stdout已被重定向。现在是管道。在情况下bash
,子shell进程正在读取文件的内容(在此处/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)
并且and 的扩展$(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
with 的内容,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
看看s如何$(cat <&3)
读取第一个之后剩下的内容,并将printf
stdout位置移到该位置之后,以便下一个printf
输出之后读取的内容。
echo x
这与echo x > /dev/stdout
stdout不会进入管道或某些字符设备(如tty设备)不同。例如,如果将stdout转到常规文件,echo x > /dev/stdout
则会截断该文件并替换其内容,x\n
而不是x\n
在当前stdout位置写入。