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内容,因为前一个文件覆盖了outwith 的内容,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/stdoutstdout不会进入管道或某些字符设备(如tty设备)不同。例如,如果将stdout转到常规文件,echo x > /dev/stdout则会截断该文件并替换其内容,x\n而不是x\n在当前stdout位置写入。