颜林林的个人网站

深入理解标准输入输出设备

2022-01-22 00:21

Image (题图来自Linux中/usr/include/unistd.h)

今天回归技术,我来尝试解释几个计算机技术中的基础概念。写技术入门的文章,我目前经验还有限,语言表达可能存在冗繁晦涩,还请各位包涵,我会继续努力锻炼并提高自己,也真心希望这类分享能对某些入门者有用。

1

记得很久很久以前,我刚学习计算机那会儿,就见过一个示意图,展示了组成计算机的五大基本部件:控制器、运算器、存储器、输入及输出。我凭记忆画了一个:

Image

这图虽然简单,但我一开始的理解,却奔着错误的方向,闹了笑话。我错误地试图把这五个框,跟五个看得见摸得着的实体部件进行等同关联。于是:键盘是“输入”,打印机是“输出”,这两样也还算正常。接下来,能够存储文件数据的,当然就是从软驱中吐出的磁盘盘片了,这是“存储器”,倒也勉强说得通。紧接着,胖乎乎的卧式主机成了“运算器”,蹲在主机上的大脑袋显示器成了“控制器”,这就很离谱了。

我特意从网上找了一个比较接近的老式电脑的照片,来供各位感受下我当时是如何“直观”误解的:

Image

(图片来自网络)

终于,在经过后来的很长时间,学习了各种计算机语言后,我才逐步纠正了最初的误解,并将相应的知识概念,与其对应的正确理解,重新进行了关联。上述五个部件,其实更多是在描述CPU和主板(当然也包括主板上插着的其他板卡,如内存条)的最基础功能,是逻辑上的示意框图。

2

在这五个部件中,横向相连的三个方框,所组成的一条信息流,却诠释了贯穿所有计算机程序的普适逻辑:

Image

任何程序都逃不开这个逻辑。

Linux系统的设计,源自更早的Unix系统,遵循着一些简单而富有强大可伸缩性的理念,堪称艺术。举个例子,最普遍的文本处理功能,Linux并不是一次性设计一个极其复杂的程序,包罗万象各种琐碎功能。相反,它把这些功能拆解开来,简化成一个个独立的单一命令,分别实现,然后用一套灵活的命令执行方法将其组合起来,完成各种复杂的需求目的。比如,head命令只截取文件的前N行,tail命令只截取文件末尾N行,grep只进行搜索,wc只进行计数。

这些简单命令,都遵循一个基础约定,所处理的数据,缺省来自键盘输入,即“标准输入设备(standard input,简写为stdin)”,而处理完成的结果,也缺省丢到屏幕上进行显示,即“标准输出设备(standard output,简写为stdout)”。而通过管道操作,用户可以轻易用磁盘上的某个文件,替换键盘或屏幕,作为输入或输出。

当然,任何程序都有可能出错,如何帮助用户更好地监控程序是否出错呢?于是就有必要让程序对外有个输出错误信息的途径。这就是“标准错误设备(standard error output,简写为stderr)”

3

关于stdin、stdout和stderr的设定,虽然源于Unix或Linux,但因为这两个系统都是由C语言写成。而C语言的经典江湖地位,使得这样一个设定深入人心,也扩散到其他各类操作系统中,获得直达底层的特别支持。

如下的C语言代码,可以在不同的系统上得到同样的表现:

#include <stdio.h>

void foo() { fprintf(stdout, “write to standard output.\n”); fprintf(stderr, “write to standard error output.\n”); } 其他高级语言也类似,在读写文件的函数中,必然有关于文件的句柄或描述符,是可以被替换成为预定义的 stdin 、 stdout 或 stderr 的(不同语言可能在名称上存在细微差别)。

而如题图所示,这三个标准输入输出设备,其文件描述符(在操作系统底层一般会定义为一个整数),通常分别被定义为 0、1 和 2。

Image

在Linux中,各种外部设备都会映射成为文件,这简化了编程接口的设计,使对所有设备的控制,都可以采用文件读写的方式进行。标准输入输出设备也不例外,下图展示了它们的设备文件信息:

Image

这里,我们又一次看到了这三个设备的预定义数字 0、1 和 2。

这三个数字很重要,是因为我们在使用shell命令时,也经常会与它们相遇,甚至有时候需要借助它们,才能实现期望的功能。

4

最后,来看一些实际命令,体会下与标准输入输出设备的相关用法:

$ cat 上述命令执行后,会等待键盘(即缺省标准输入设备)的输入,并在回车后,将所输入的内容,重新输出到屏幕(即缺省标准输出设备)。按下 Ctrl+C 或 Ctrl+D 即可退出该命令。 $ cat > test.txt 上述命令执行后,同样会等待键盘输入,但不同的是,这次所输入的内容,将会写入文件 test.txt 中,而不会输出到屏幕。 $ echo “this is to stdout” $ echo “this is to stderr” 1>&2 上面两行命令,都是用 echo 命令来输出相应文本的。第一条命令输出到缺省的“标准输出设备stdout”,这很容易理解。而第二条命令,因为末尾加了一个“1>&2”,它表示把文件描述符 1 更换对接到 文件描述符 2 上,还记得前面提及的数字表示么,这就是把“标准输出设备”重定向到“标准错误设备”,也就是说,这条命令是把文本输出到了“标准错误设备”。 这是 shell 脚本中最常用到的写法,经常会用它把一些程序运行状态输出出来,方便导入到日志中,而不是错误地写到了输出结果文件中。 $ XXX –help 在Linux中,若遇到不会使用的命令,我们经常需要查看其帮助文档,最简单的做法,是使用上述形式,加一个“–help”的参数。如果帮助内容很多,屏幕会哗哗刷过大量信息,什么都看不清。这时管道就可以帮上大忙: $ XXX –help | less 有时候,这个方法并不好使,似乎帮助信息还是转瞬即逝,那是由于这些命令把帮助信息丢到了 stderr 而非 stdout。有两个方法可以解决这种问题: $ XXX –help 2>&1 | less $ XXX –help |& less 前一条命令,使用“2>&1”进行了从“标准错误”到“标准输出”的重定向;而后一条命令,则使用了“|&”,说明该管道后面接的命令,将以其“标准错误”作为数据输入源。 最后,再来个技巧性很强的:如果想要交换“标准输出”和“标准错误”(这是个有点奇怪,但偶尔遇到时,可能还真有点挑战的需求),则可以用如下命令: $ bash test.sh 3>&2 2>&1 1>&3- 它用了一个文件描述符 3 做中转,交换了两者,而末尾的“-”,表示最终把 3 这个临时文件描述符给关掉,然后就完美实现了两个设备的交换。

--- END ---

注:本文首发表于“不靠谱颜论”公众号,并同步至本站。