2.2.1 重定向
重定向是指改变进程输入/输出的对象。例如Linux中进程默认输出的对象是显示器,但很多时候,用户需要改变进程输出的对象,例如将它的输出保存到文件中,便于debug查错,此时就要用到重定向。一个进程的输入和输出有哪些,分别是和哪些设备关联的,如何改变进程的输入和输出,如何使得一个进程的输出成为另一个进程的输入,以及如何进行实际的重定向操作等等,这些都是本节要说明和解决的问题。
1.进程默认的标准I/O流文件
每个进程创建时都会打开3个标准I/O流文件。
● 标准输入流文件,使用stdin表示,通常对应键盘的输入,文件描述符为0,C语言中调用的scanf函数读取的就是stdin。
● 标准输出文件,使用stdout表示,通常对应屏幕的输出,文件描述符为1,C语言中调用的printf函数就是向stdout写入数据。
● 标准错误输出文件,使用stderr表示,文件描述符为2,C语言中调用的perror函数就是向stderr写入数据。
默认情况下,stdin、stdout和stderr会指向同一个文件,即控制终端的设备文件,具体示例说明如下。
Linux使用控制终端(Control Terminal)来统一表示进程所关联的终端,具体内容参考本书配套免费电子书《Linux快速入门与实战——扩展阅读与实践教程》中的“扩展阅读2:Linux交互过程”部分。
(1)运行在虚拟终端上的进程
1)打印虚拟终端信息,系统显示/dev/tty1,如图2-2所示。在该终端上运行ping程序,命令如图2-2所示,那么ping进程的控制终端对应的设备文件就是/dev/tty1。
图2-2 ping运行图
2)使用lsof来查看ping所打开的文件,命令如下。
lsof是一个Linux命令,它可以列出进程和文件之间的关系,例如列出某个进程打开的所有文件,或者是列出打开了某个文件的所有进程等。本书使用lsof只是为前面所述结论提供一种验证手段。CentOS 8最小化安装中并没有自带lsof命令,如果要使用该命令,需要参考3.2节中的方法自行安装。
lsof输出结果如下,可以看到ping进程的stdin(对应文件描述符0)、stdout(对应文件描述符1)和stderr(对应文件描述符2)打开的文件都是/dev/tty1,即控制终端的设备文件。
(2)运行在伪终端上的进程
1)在伪终端上运行ping程序,命令如下,此时ping进程控制终端的设备文件是/dev/pts/0。
2)使用lsof来查看ping所打开的文件,命令如下。
同样可以看到ping进程的stdin(对应文件描述符0)、stdout(对应文件描述符1)和stderr(对应文件描述符2)打开的文件都是/dev/pts/0,即控制终端的设备文件。
综上所述,默认情况下,进程的stdin、stdout和stderr都同控制终端的设备文件相关联。
2.重定向操作示例。
综上所述,所谓重定向,就是解除进程的stdin、stdout和stderr 同控制终端的设备文件的关联,转而和其他文件相关联。本小节将介绍典型的重定向操作示例,具体说明如下。
重定向可以带来很多好处:假如stdin变成普通文件,scanf读取的就是普通文件,而不是键盘的输入,这样可以实现自动重复输入,在测试时特别有用;如果stdout是普通文件,printf的内容就会输出到普通文件中存储起来,即使程序退出也可以看到之前程序所输出的信息。
(1)示例1:使用 < 实现stdin重定向
1)使用read命令从stdin中读取一行数据,并将其保存在指定的变量name中。具体命令如下,此时stdin是控制终端的设备文件,read读取的是键盘输入。
2)在键盘上输入hello,如下所示。
3)按〈Enter〉键后,read读取hello,存储在变量name中并返回。使用echo显示变量name的值进行验证。
4)使用< 将read的stdin和普通文件/etc/hostname相关联。命令如下,此时read命令不再读取键盘输入,而是以/etc/hostname文件的内容作为输入。
5)该命令执行后,使用echo来显示变量name的值,进行验证,结果显示如下,name中存储的是/etc/hostname的文件内容。
(2)示例2:使用 > 实现stdout重定向
1)使用echo命令将字符串输出到stdout,stdout默认是控制终端的设备文件,本例中控制终端是虚拟终端,虚拟终端的输出对应显示器,因此,echo命令执行后,会在计算机屏幕(显示器)上打印信息,如下所示。
2)使用> 将stdout和普通文件/tmp/a关联,命令如下。此时echo向stdout的输出将不再打印在屏幕上,而是输出到/tmp/a文件中。
3)打印 /tmp/a的内容进行验证,命令如下,可以看到该文件存储的内容正是echo所输出的“hello world”。
> 会清除重定向文件(如/tmp/a)的内容,如果不清除已有内容,而是要追加内容的话,可以使用 >>。
(3)示例3:stderr重定向
1)Linux命令执行的错误信息将会输出到stderr,stderr默认是控制终端的设备文件,而本例中控制终端是虚拟终端,虚拟终端的输出对应显示器,因此错误信息会打印在虚拟终端所在的计算机的屏幕上。例如普通用户由于没有访问/root的权限,“ls /root”会报错,报错信息就打印在屏幕上,命令如下。
2)使用> 将ls的stderr和普通文件相关联,这样,ls的报错信息会输出到/tmp/a文件中,而不是打印在屏幕上。注意stderr和stdout重定向的用法还有点区别,stdout的重定向直接使用> 即可,stderr如果也直接使用>的话,就会和stdout混淆在一起。因此,bash使用数字2来表示stderr,再加上重定向输出符号 >,就可以表示stderr的重定向了。命令如下,注意数字2和> 之间不能有空格,>和/tmp/a之间可以有空格也可以没有空格,命令如下。
bash使用数字1表示stdout,这样,分别用1和2就可以同时实现stdout和stderr的重定向了。由于bash默认的输出是stdout,因此,一般情况下不需要用数字1来指定stdout。
(4)示例4:stdout和stderr重定向混合使用
重定向中,2>&1是常见的用法,例如下面的命令实现了将stderr和stdout都输出到/dev/null的功能。
上述命令说明如下。
● >/dev/null中 > 前面没有数字,默认是stdout,即stdout是/dev/null文件,/dev/null是一个特殊的字符设备文件,它会丢弃所有向它写入的内容,又称为空设备。
● 2>&1中2表示stderr,即stderr同&1文件相关联,那么&1又表示什么文件呢?前面说过数字1表示stdout,那是1在 >的左边没有歧义。但此处1在 >的右边,既可以表示名字为1的普通文件,也可以表示stdout,因此,bash在1前面加上一个&,那么&1就表示stout,这样就不会混淆。总之,2>&1表示stderr就是stdout,它们关联同一个文件。加上前面指定了stdout是/dev/null,因此,stderr也是/dev/null。
● 因为/dev/null是空文件,stdin和stdout都是/dev/null,因此,该命令的标准输出信息和错误信息都会被丢弃。
(5)示例5:使用管道 | 实现重定向
管道 | 可以使得进程A的输出,作为进程B的输入,这样,进程A的输出本来是要打印在屏幕上的,结果被重定向到进程B,而进程B本来是要从键盘读取数据的,结果被重定向到A。
管道有多个好处:首先,它可以实现多个命令的组合,每个命令完成一项工作,组合起来就可以完成复杂的工作;其次,命令之间数据的传输直接通过管道完成,而不用通过写入/读取硬盘上的文件来完成,这样可以大大降低I/O开销。
管道使用示例如下,ps -A可以输出所有进程的信息,如果想知道哪个进程的控制终端是tty1,就可以使用grep命令来过滤ps-A的输出信息,得到包含tty1的字符串,命令如下。
上述命令和参数说明如下。
● ps-A输出所有进程的信息。
● 管道 | 将ps-A的输出作为grep命令的输入。
● grep tty1以ps-A输出的内容作为输入,过滤得到所有包含tty1的字符串。