文件描述符是与输入和输出流相关联的整数。最广为人知的文件描述符是stdin
、stdout
和stderr
。我们可以将某个文件描述符的内容重定向到另一个文件描述符中。下面展示了一些文件描述符操作和重定向的例子。
Shell 玩转文件描述符与重定向 预备知识
在编写脚本的时候会频繁用到标准输入(stdin
)、标准输出(stdout
)和标准错误(stderr
)。脚本可以使用大于号将输出重定向到文件中。命令产生的文本可能是正常输出,也可能是错误信息。默认情况下,正常输出(stdout
)和错误信息(stderr
)都会显示在屏幕上。我们可以分别为其指定特定的文件描述符来区分两者。
文件描述符是与某个打开的文件或数据流相关联的整数。文件描述符0
、1
以及2
是系统预留的。
0
——stdin
(标准输入)。-
1
——stdout
(标准输出)。 -
2
——stderr
(标准错误)。
Shell 玩转文件描述符与重定向 实战演练
(1) 使用大于号将文本保存到文件中:
$ echo "This is a sample text 1" > temp.txt
该命令会将输出的文本保存在temp.txt中。如果temp.txt已经存在,大于号会清空该文件中先前的内容。
(2) 使用双大于号将文本追加到文件中:
$ echo "This is sample text 2" >> temp.txt
(3) 使用cat
查看文件内容:
$ cat temp.txt
This is sample text 1
This is sample text 2
接着来看看如何重定向stderr
。当命令产生错误信息时,该信息会被输出到stderr
流。考虑下面的例子:
$ ls +
ls: cannot access +: No such file or directory
这里,+
是一个非法参数,因此会返回错误信息。
成功和不成功的命令
当一个命令发生错误并退回时,它会返回一个非0的退出状态;而当命令成功完成后,它会返回为0的退出状态。退出状态可以从特殊变量$?
中获得(在命令结束之后立刻运行echo $?
,就可以打印出退出状态)。
下面的命令会将stderr
文本打印到屏幕上,而不是文件中(因为stdout
并没有输出,所以out.txt的内容为空):
$ ls + > out.txt
ls: cannot access +: No such file or directory
在下面的命令中,我们使用2>
(数字2以及大于号)将stderr
重定向到out.txt:
$ ls + 2> out.txt #没有问题
你可以将stderr
和stdout
分别重定向到不同的文件中:
$ cmd 2>stderr.txt 1>stdout.txt
下面这种更好的方法能够将stderr
转换成stdout
,使得stderr
和stdout
都被重定向到同一个文件中:
$ cmd 2>&1 alloutput.txt
或者这样
$ cmd &> output.txt
如果你不想看到或保存错误信息,那么可以将stderr
的输出重定向到/dev/null,保证一切都会被清除得干干净净。假设我们有3个文件,分别是a1、a2、a3。但是普通用户对文件a1没有“读-写-执行”权限。如果需要打印文件名以a起始的所有文件的内容,可以使用cat
命令。来设置一些测试文件:
$ echo A1 > a1
$ echo A2 > a2
$ echo A3 > a3
$ chmod 000 a1 #清除所有权限
使用通配符(a*
)显示这些文件内容的话,系统会显示出错信息,因为文件a1没有可读权限:
$ cat a*
cat: a1: Permission denied
A2
A3
其中,cat: a1: Permission denied
属于stderr
信息。我们可以将其重定向到一个文件中,同时将stdout
信息发送到终端。
$ cat a* 2> err.txt # stderr被重定向到err.txt
A2
A3
$ cat err.txt
cat: a1: Permission denied
我们在处理一些命令输出的同时还想将其保存下来,以备后用。stdout
作为单数据流(single stream),可以被重定向到文件或是通过管道传入其他程序,但是无法两者兼得。
有一种方法既可以将数据重定向到文件,还可以提供一份重定向数据的副本作为管道中后续命令的stdin
。tee
命令从stdin
中读取,然后将输入数据重定向到stdout
以及一个或多个文件中。
command | tee FILE1 FILE2 | otherCommand
在下面的代码中,tee
命令接收到来自stdin
的数据。它将stdout
的一份副本写入文件out.txt,同时将另一份副本作为后续命令的stdin
。命令cat -n
为从stdin
中接收到的每一行数据前加上行号并将其写入stdout
:
$ cat a* | tee out.txt | cat -n
cat: a1: Permission denied
1 A2
2 A3
使用cat
查看out.txt的内容:
$ cat out.txt
A2
A3
注意,
cat: a1: Permission denied
并没有在文件内容中出现,因为这些信息被发送到了stderr
,而tee
只能从stdin
中读取。
默认情况下,tee
命令会将文件覆盖,但它提供了一个-a
选项,可用于追加内容。
$ cat a* | tee -a out.txt | cat –n
带有参数的命令可以写成:command FILE1 FILE2 ...
,或者就简单地使用command FILE
。
要发送输入内容的两份副本给stdout
,使用-
作为命令的文件名参数即可:
$ cmd1 | cmd2 | cmd -
例如:
$ echo who is this | tee -
who is this
who is this
也可以将/dev/stdin作为输出文件名来代替stdin
。类似地,使用/dev/stderr代表标准错误,/dev/stdout代表标准输出。这些特殊的设备文件分别对应stdin
、stderr
和stdout
。
Shell 玩转文件描述符与重定向 工作原理
重定向操作符(>
和>>
)可以将输出发送到文件中,而不是终端。>
和>>
略有差异。尽管两者都可以将文本重定向到文件,但是前者会先清空文件,然后再写入内容,而后者会将内容追加到现有文件的尾部。
默认情况下,重定向操作针对的是标准输出。如果想使用特定的文件描述符,你必须将描述符编号置于操作符之前。
>
等同于1>
;对于>>
来说,情况也类似(即>>
等同于1>>
)。
处理错误时,来自stderr
的输出被倾倒入文件/dev/null中。./dev/null是一个特殊的设备文件,它会丢弃接收到的任何数据。null设备通常也被称为黑洞,因为凡是进入其中的数据都将一去不返。
Shell 玩转文件描述符与重定向 补充内容
从stdin
读取输入的命令能以多种方式接收数据。可以用cat
和管道来指定我们自己的文件描述符。考虑下面的例子:
$ cat file | cmd
$ cmd1 | cmd2
- 将文件重定向到命令
借助小于号(<
),我们可以像使用stdin
那样从文件中读取数据:
$ cmd < file
- 重定向脚本内部的文本块
可以将脚本中的文本重定向到文件。要想将一条警告信息添加到自动生成的文件顶部,可以使用下面的代码:
$ cat log.txt
This is a generated file. Do not edit. Changes will be overwritten.
- 自定义文件描述符
文件描述符是一种用于访问文件的抽象指示器(abstract indicator)。存取文件离不开被称为“文件描述符”的特殊数字。0
、1
和2
分别是stdin
、stdout
和stderr
预留的描述符编号。
exec
命令创建全新的文件描述符。如果你熟悉其他编程语言中的文件操作,那么应该对文件打开模式也不陌生。常用的打开模式有3种。
- 只读模式。
- 追加写入模式。
- 截断写入模式。
<
操作符可以将文件读入stdin
。>
操作符用于截断模式的文件写入(数据在目标文件内容被截断之后写入)。>>
操作符用于追加模式的文件写入(数据被追加到文件的现有内容之后,而且该目标文件中原有的内容不会丢失)。文件描述符可以用以上3种模式中的任意一种来创建。
创建一个用于读取文件的文件描述符:
$ exec 3<input.txt #使用文件描述符3打开并读取文件
我们可以这样使用它:
$ cat<&3
this is a test line
如果要再次读取,我们就不能继续使用文件描述符3
了,而是需要用exec
重新创建一个新的文件描述符(可以是4
)来从另一个文件中读取或是重新读取上一个文件。
创建一个用于写入(截断模式)的文件描述符:
$ exec 4>output.txt #打开文件进行写入
例如:
$ exec 5>>input.txt
例如:
$ exec 5>>input.txt
$ echo appended line >&5
$ cat input.txt
newline
appended line