linux 的输入与输出设备

Linux 默认提供了三个特殊设备,用于终端的显示和输出,分别为stdin(标准输入,对应于你在终端的输入),stdout(标准输出,对应于终端的输出),stderr(标准错误输出,对应于终端的输出)。

image.png

文件描述符

文件描述符在形式上是一个非负整数。本质上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。

当程序打开一个现有文件或者创建一个文件时,内核会向进程返回一个文件描述符。

默认情况下,使用终端的标准输入作为命令的输入和标准输出作为命令的输出。

标准错误重定向

我们可以将结果从屏幕重定向到某个文件。
image.png

可是,错误的信息还是显示在了屏幕上,而重定向的文件中也只有被cat 获取到的文件中的文本信息。

由此可见,标准输出和标准错误并不一样。

有两种解决的方法:

我们可以指定标准错误的描述符2 重定向到描述符1。或直接通过 &> ,表示将标准错误与标准输出同时定向到指定文件。

  1. # 将标准错误重定向到标准输出,再将标准输出重定向到文件,注意要将重定向到文件写到前面
  2. $ cat Documents/test.c hello.c >somefile 2>&1
  3. # 或者只用bash提供的特殊的重定向符号"&"将标准错误和标准输出同时重定向到文件
  4. $ cat Documents/test.c hello.c &>somefilehell

tee 命令

tee 可以帮助我们将结果同时定向到文件和屏幕。

需要注意,tee 本身并不能读取文件,一般通过管道符将文本信息传递给tee 后再进行使用。

$ echo 'hello shiyanlou' | tee hello
# 将echo 结果输出屏幕,并保存在hello 文件中。

永久重定向

有的时候,我们可能希望脚本中的某些内容永久的重定向到某个文件中,比如操作的日志。这时候可以使用 exec

# 先开启一个子 Shell
$ zsh
# 使用exec替换当前进程的重定向,将标准输出重定向到一个文件
$ exec 1>somefile
# 后面你执行的命令的输出都将被重定向到文件中,直到你退出当前子shell,或取消exec的重定向(后面将告诉你怎么做)
$ ls
$ exit
$ cat somefile

这时候,所有的标准输出结果都不会打印在屏幕,而是在指定的文件中了。
image.png

自定义文件描述符

在 Shell 中有 9 个文件描述符。上面我们使用了也是它默认提供的 0,1,2 号文件描述符。另外我们还可以使用 3-8 的文件描述符,只是它们默认没有打开而已。

查看文件描述符

你可以使用下面命令查看当前 Shell 进程中打开的文件描述符:

$ cd /dev/fd/;ls -Al

image.png

创建新的文件描述符

和永久重定向一样, exec 也可以用来设定新的文件描述符。
接着我们就可以像设定标准错误重定向那样,将输出结果重定向到 &3 三号文件描述符。

$ zsh
$ exec 3>somefile
# 先进入目录,再查看,否则你可能不能得到正确的结果,然后再回到上一次的目录
$ cd /dev/fd/;ls -Al;cd -
# 注意下面的命令>与&之间不应该有空格,如果有空格则会出错
$ echo "this is test" >&3
$ cat somefile
$ exit

关闭文件描述符

直接将相应的文件描述符重定向到 &- ,就会关闭它。

$ exec 3>&-
$ cd /dev/fd;ls -Al;cd -

屏蔽命令输出

在 Linux 中有一个被称为“黑洞”的设备文件,所有导入它的数据都将被“吞噬”—— /dev/null 。它叫做空设备,是一个特殊的设备文件,它通常被用于丢弃不需要的输出流,或作为用于输入流的空文件,这些操作通常由重定向完成。读取它则会立即得到一个 EOF。

如果将标准输出重定向到这个“黑洞”,则就会完全得不到任何的输出结果了。

$ cat Documents/test.c 1>/dev/null 2>&1

练习

下面代码为什么是错的?

while read filename; do
  rm -iv $filename
done <<(ls)

答案:原本代码是希望通过输入重定向,将ls 结果传递给while 循环,实现删除所有ls 列出的文件。
然而rm 中的i 选项会从标准输入中找寻命令以获得用户的删除许可(y/n),而ls 中并不存在相关文件,等待遍历完ls 后,便会终止命令。