困惑于stdin、stdout和stderr?

310
我对这三个文件的用途感到相当困惑。如果我的理解正确,stdin是程序写入其请求以运行进程任务的文件,stdout是内核写入其输出并由请求该信息的进程访问的文件,stderr则是所有异常都输入的文件。但是当我打开这些文件以检查它们是否真的存在时,似乎没有任何迹象表明它们确实存在!

我想知道的是,这些文件的确切目的是什么,简单易懂的答案,尽量避免使用技术术语!

11个回答

333

标准输入 - 这是您的进程从中读取信息的文件句柄.

标准输出 - 您的进程将常规输出写入此文件句柄.

标准错误 - ���的进程将诊断输出写入此文件句柄。

这基本上是我能够制作的最简化版本 :-)

当然,这主要是按照惯例来做的。如果您愿意,没有什么阻止您将诊断信息写入标准输出。您甚至可以完全关闭这三个文件句柄并打开自己的文件进行I/O。

当您的进程启动时,它应该已经打开了这些句柄,并且可以从中读取和/或写入。

默认情况下,它们可能连接到您的终端设备(例如/dev/tty),但在您的进程启动之前,shell 将允许您建立这些句柄与特定文件和/或设备之间的连接(甚至是管道连接到其他进程)(一些可能的操纵方式相当聪明)。

一个示例是:

my_prog <inputfile 2>errorfile | grep XYZ

以下操作将会实现:

  • 创建一个名为 my_prog 的进程。
  • inputfile 打开并作为该进程的标准输入(文件句柄 0)。
  • errorfile 打开并作为该进程的标准错误(文件句柄 2)。
  • 创建另一个名为 grep 的进程。
  • my_prog 的标准输出附加到 grep 的标准输入。

关于您的评论:

当我在 /dev 文件夹中打开这些文件时,为什么我从来没有看到运行进程的输出?

因为它们不是普通文件。尽管UNIX将每件事都呈现为某个文件系统中的文件,但在最底层并非如此。在 /dev 层次结构中的大多数文件都是字符设备或块设备,实际上是设备驱动程序。它们没有大小,但有主设备号和次设备号。

当您打开它们时,您连接的是设备驱动程序而不是物理文件,并且设备驱动程序足够聪明,以知道应分别处理不同的进程。

对于Linux的 /proc 文件系统也是如此。它们不是真实的文件,只是对内核信息进行严格控制的网关。


3
谢谢你的回复。我能够理解你描述的文件目的,但我希望更进一步。当我在/dev文件夹中打开这些文件时,为什么我从未看到运行进程的输出呢?比如说我在终端上执行top命令,它不应该会周期性地将结果输出到stdout文件中吗?因此,当更新时,我应该能够看到输出的实例被打印到这个文件中。但事实并非如此。那么这些文件和/dev目录中的文件是否相同呢? - Shouvik
10
因为那些并不是严格意义上的文件,它们是设备节点,用于指明特定设备的写入位置。UNIX可能会将所有内容呈现为文件抽象,但这并不代表在最深层次上就是这样。 - paxdiablo
30
请注意,@Shouvik,/dev/stdin 是一个符号链接,指向当前正在运行的程序打开的第一个文件描述符 /proc/self/fd/0。因此,由 /dev/stdin 指向的内容会因程序而异,因为 /proc/self/ 总是指向“当前正在运行的程序”。(即执行 open 调用的任何程序。)/dev/stdin 和其他类似的文件是为了让 setuid shell 脚本更加安全,并可以将文件名 /dev/stdin 传递给仅处理文件的程序,但您需要进行更多交互控制。(有一天这将是你需要知道的一个有用技巧。 :) - sarnold
2
@CarlosW.Mercado,文件是数据的物理表示形式。例如,硬盘上存储的位。文件句柄通常是用于引用打开的文件的小标记。 - paxdiablo
2
@Oswin,我已经更改了使用ISO标准中的术语(传统和诊断),因为那是权威来源。 - paxdiablo
显示剩余12条评论

89
更准确的说,stdinstdoutstderr是"I/O流"而不是文件。正如您所注意到的,这些实体并不存在于文件系统中。但是Unix哲学在I/O方面是"一切皆文件",事实上意味着您可以使用相同的库函数和接口(printfscanfreadwriteselect等),无需担心I/O流连接到键盘、磁盘文件、套接字、管道或其他I/O抽象。
大多数程序需要读取输入、写入输出和记录错误,因此stdinstdoutstderr作为编程便利已经预定义好了。这只是一个约定,并不是由操作系统强制执行的。

谢谢您的回复。您是否知道我如何拦截进程的输出数据流并将其输出到自己的文件中? - Shouvik
@Shouvik,你可以使用"管道"来实现。以echo程序为例:echo 'foo' > target.txt。通常,echo将其输出发送到标准输出(stdout),而在这里我们将输出重定向到target.txt文件中。 - Niels Bom

81

作为对以上答案的补充,以下是关于重定向的总结:

Redirections cheatsheet

编辑:这张图片并不完全正确。

第一个例子根本没有使用标准输入(stdin),而是将“hello”作为参数传递给echo命令。

此图还说2>&1与&>具有相同的效果,但实际上并非如此。

ls Documents ABC > dirlist 2>&1
#does not give the same output as 
ls Documents ABC > dirlist &>

这是因为 &> 需要重定向到一个文件,而 2>&1 只是将标准错误输出到标准输出


7
你的评论结合已接受的回答,表述得非常清晰易懂,完美解释了事情!谢谢! - Mykola
4
一张图片胜过千言万语! - tauseef_CuriousGuy
1
&>需要重定向文件,但2>&1不需要。 &>用于记录日志,但2>&1可用于同时记录日志和终端STDOUT STDERR视图。 2>&1仅用于查看程序的STDOUT和STDERR(取决于程序处理错误的能力)在终端的命令提示符中。 - Vasil
2
实际上,第一个是完全错误的。 "hello" 绝不会与 echo 的标准输入交互。事实上,如果您将 echo 替换为一个 读取 标准输入的程序,它只会坐在那里等待您的终端输入。在这种情况下,hello 作为参数提供(通过 argc/argv)。关于 2>&1&> 具有相同效果的评论是准确的,如果您将效果视为所述的:“将 stderr 与 stdout 合并”。它们都可以做到这一点,但方式略有不同。等价性将是 > somefile 2>&1&> somefile - paxdiablo
1
最好将图形重写为文本,这样您就可以轻松地删除第一个了 :-) - paxdiablo

30
很抱歉,您的理解完全相反了。 :)
从程序的角度考虑,“标准输入”、“标准输出”和“标准错误”,而不是从内核的角度考虑。
当程序需要打印输出时,通常会打印到“标准输出”。程序通常使用“printf”将输出打印到标准输出,该函数仅打印到标准输出。
当程序需要打印错误信息(不一定是异常,这些是编程语言构造,在更高的级别上强制执行),通常会打印到“标准错误”。通常使用“fprintf”将其打印到标准错误,并接受要在打印时使用的文件流。文件流可以是任何已打开用于写入的文件:标准输出、标准错误或使用“fopen”或“fdopen”打开的任何其他文件。
当文件需要读取输入时,使用“标准输入”,使用“fread”或“fgets”或“getchar”。
这些文件中的任何一个都可以轻松地从shell中进行重定向,例如:
cat /etc/passwd > /tmp/out     # redirect cat's standard out to /tmp/foo
cat /nonexistant 2> /tmp/err   # redirect cat's standard error to /tmp/error
cat < /etc/passwd              # redirect cat's standard input to /etc/passwd

或者,整个大餐:
cat < /etc/passwd > /tmp/out 2> /tmp/err

有两个重要的注意点:首先,“标准输入”、“标准输出”和“标准错误”只是一种约定俗成的说法。尽管它们是一种非常强的约定,但实际上只是一个协议,使得像这样运行程序非常方便:grep echo /etc/services | awk '{print $2;}' | sort,并且每个程序的标准输出都可以连接到下一个程序在管道中的标准输入上。

其次,我给出了用于处理文件流(FILE *对象)的标准ISO C函数。在内核层面上,实际上是所有的文件描述符(int引用文件表) 和更低级别的操作,如readwrite,它们不会像ISO C函数那样进行快乐的缓冲。我选择使用更简单的函数来保持简单易懂,但我认为你也应该知道其他选择:)


在执行过程中,它是将错误写入此 stderr 文件还是在从源代码编译程序时。另外,当我们从编译器的角度来看这些文件时,与从程序的角度来看是否不同? - Shouvik
1
@Shouvik,编译器只是另一个程序,具有自己的stdin、stdout和stderr。当编译器需要写警告或错误时,它会将它们写入stderr。当编译器前端为汇编器输出中间代码时,它可能会在stdout上写入中间代码,而汇编器可能会在stdin上接受其输入,但所有这些都是从用户角度看来的幕后操作。一旦您拥有了一个已编译的程序,该程序也可以将错误写入其标准错误,但这与被编译无关。 - sarnold
谢谢你提供的信息。我想不从那个角度看它,真是太愚蠢了... :P - Shouvik
1
那么您的意思是标准可以帮助我们打印程序。 - babygame0ver

21

我认为人们说只有错误信息才应该使用stderr是误导性的。

stderr还应用于那些旨在为运行命令的用户提供信息而不是任何潜在下游数据的消费者的信息性消息(例如,如果您运行一系列命令的shell管道,您不希望像“获取项目 30 of 42424”这样的信息性消息出现在stdout上,因为它们会让消费者感到困惑,但您可能仍然希望用户看到它们)。

参见此文以了解历史上的理由:

“所有程序都将诊断信息放在标准输出中。当输出被重定向到文件时,这常常引起问题,但当输出被发送到不知情的进程时,这种情况变得无法忍受。尽管如此,人们还是不愿违反标准输入标准输出模型的简单性,经过v6时期的容忍,直到Dennis Ritchie引入标准错误文件来剪断Gordian结。不过这还不够。在管道中,诊断信息可能来自同时运行的多个程序。诊断信息需要标识自己。”


历史理由链接已经失效 - 域名已经不存在了! - Meitham
1
已替换为archive.org链接。 - dee

12

标准输入

从控制台读取输入(例如键盘输入)。 在C语言中与scanf一起使用。

scanf(<formatstring>,<pointer to storage> ...);

标准输出

将内容输出到控制台。在 C 语言中使用 printf 函数。

printf(<string>, <values to print> ...);

stderr

将“错误”输出发送到控制台。 在C中与fprintf一起使用。

fprintf(stderr, <string>, <values to print> ...);

重定向

标准输入的来源可以被重定向。例如,它不一定来自于键盘输入,可以来自文件(echo < file.txt)或另一个程序 (ps | grep <userid>).

标准输出和标准错误也可以被重定向。例如,标准输出可以被重定向到文件:ls . > ls-output.txt,在这种情况下,输出会被写入到文件ls-output.txt标准错误可以用2>进行重定向


正如上面提到的那样,仅仅使用 echo < file.txt 是行不通的。因为 echo 命令接受字符串作为参数,而不是读取标准输入。 - ordem

5
使用ps -aux命令可以查看当前进程,所有进程都列在/proc/目录下的/proc/(pid)/中,通过调用cat /proc/(pid)/fd/0来打印该进程标准输出中的任何内容。因此,可能是这样的:

/proc/(pid)/fd/0 - 标准输出文件
/proc/(pid)/fd/1 - 标准输入文件
/proc/(pid)/fd/2 - 标准错误文件

例如my terminal window

但是,只有/bin/bash工作得很好,其他进程通常在0中没有任何内容,但在2中写入了许多错误信息。


4

想要获取关于这些文件的权威信息,请查看man页面,在终端中运行命令即可。

$ man stdout 

简单来说,每个文件都用于:

stdout 输出流

stdin 输入流

stderr 打印错误或日志信息。

每个 Unix 程序都有这些流中的每一个。


2

stderr不会进行IO缓存,因此如果我们的应用程序需要将关键信息(例如错误、异常)打印到控制台或文件中,请使用它,而使用stdout打印一般日志信息,因为它使用IO缓存,存在一种情况,即在将消息写入文件之前,应用程序可能会关闭,从而使调试变得复杂。


0

具有关联缓冲区的文件称为流,声明为指向已定义类型FILE的指针。fopen() 函数会为流创建某些描述性数据,并返回一个指针以在所有后续交易中指定该流。通常,在头文件中声明了三个具有恒定指针的打开流,它们与标准打开文件相关联。

在程序启动时,预定义了三个流,无需显式打开:标准输入(用于读取传统输入)、标准输出(用于写入传统输出)和标准错误(用于写入诊断输出)。当打开标准错误流时,它不是完全缓冲的;仅当可以确定流不引用交互式设备时,标准输入和标准输出流才是完全缓冲的。

https://www.mkssoftware.com/docs/man5/stdio.5.asp


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接