输入输出重定向
文章目录
标准文件描述符
Linux系统将每个对象当作文件处理。这包括输入和输出进程。Linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开 的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell保留了前三个文 件描述符(0、1和2).
STDIN
STDIN文件描述符代表shell的标准输入。对终端界面来说,标准输入是键盘。shell从STDIN 文件描述符对应的键盘获得输入,在用户输入时处理每个字符。
在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。 它会读取文件并提取数据,就如同它是键盘上键入的。
STDOUT
STDOUT文件描述符代表shell的标准输出。在终端界面上,标准输出就是终端显示器。shell 的所有输出(包括shell中运行的程序和脚本)会被定向到标准输出中,也就是显示器。
默认情况下,大多数bash命令会将输出导向STDOUT文件描述符。你可以用输出重定向来改变。你也可以将数据追加到某个文件。这可以用»符号来完成。
STDERR
shell通过特殊的STDERR文件描述符来处理错误消息。STDERR文件描述符代表shell的标准错误输出。shell或shell中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。
默认情况下,STDERR文件描述符会和STDOUT文件描述符指向同样的地方(尽管分配给它们 的文件描述符值不同)。也就是说,默认情况下,错误消息也会输出到显示器输出中。
但STDERR并不会随着STDOUT的重定向而发生改变。
重定向错误
只重定向错误
STDERR文件描述符被设成2。可以选择只重定向错误消息,将该文 件描述符值放在重定向符号前。该值必须紧紧地放在重定向符号前,否则不会工作。
ls -al badfile 2> test4
现在运行该命令,错误消息不会出现在屏幕上了。该命令生成的任何错误消息都会保存在输 出文件中。用这种方法,shell会只重定向错误消息,而非普通数据。
重定向错误和数据
如果想重定向错误和正常输出,必须用两个重定向符号。需要在符号前面放上待重定向数据 所对应的文件描述符,然后指向用于保存数据的输出文件。
$ ls -al test test2 test3 badtest 2> test6 1> test7
可以用这种方法将脚本的正常输出和脚本生成的错误消息分离开来。这样就可以轻松地识别出错误信息,再不用在成千上万行正常输出数据中翻腾了。
另外,如果愿意,也可以将STDERR和STDOUT的输出重定向到同一个输出文件。为此bash shell 提供了特殊的重定向符号&>。
在脚本中重定向输出
可以在脚本中用STDOUT和STDERR文件描述符以在多个位置生成输出,只要简单地重定向相 应的文件描述符就行了。有两种方法来在脚本中重定向输出:
- 临时重定向行输出
- 永久重定向脚本中的所有命令
临时重定向
如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到STDERR。你所需要做的 是使用输出重定向符来将输出信息重定向到STDERR文件描述符。在重定向到文件描述符时,你必须在文件描述符数字之前加一个&:
echo "This is an error message" >&2
这行会在脚本的STDERR文件描述符所指向的位置显示文本,而不是通常的STDOUT.
记住,默认情况下,Linux会将STDERR导向STDOUT。但是,如果你在运行脚本时重定向了 STDERR,脚本中所有导向STDERR的文本都会被重定向。
$ ./test8 2> test9
This is normal output
$ cat test9
This is an error
$
永久重定向
如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很烦琐。取而代之,你可 以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。
exec 1>testout
exec命令会启动一个新shell并将STDOUT文件描述符重定向到文件。脚本中发给STDOUT的所 有输出会被重定向到文件。
在脚本中重定向输入
你可以使用与脚本中重定向STDOUT和STDERR相同的方法来将STDIN从键盘重定向到其他位置。exec命令允许你将STDIN重定向到Linux系统上的文件中:
exec 0< testfile
这个命令会告诉shell它应该从文件testfile中获得输入,而不是STDIN。这个重定向只要 在脚本需要输入时就会作用。
$ cat test12
#!/bin/bash
# redirecting file input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
$ ./test12
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
$
创建自己的重定向
在shell 中最多可以有9个打开的文件描述符。其他6个从3~8的文件描述符均可用作输入或输出重定向。 你可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。
创建输出文件描述符
可以用exec命令来给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件 描述符分配给一个文件,这个重定向就会一直有效,直到你重新分配。
exec 3>test13out
echo "and this should be stored in the file" >&3
这个脚本用exec命令将文件描述符3重定向到另一个文件。当脚本执行echo语句时,输出内 容会像预想中那样显示在STDOUT上。但你重定向到文件描述符3的那行echo语句的输出却进入 了另一个文件。这样你就可以在显示器上保持正常的输出,而将特定信息重定向到文件中(比如 日志文件)。
也可以不用创建新文件,而是使用exec命令来将输出追加到现有文件中。
exec 3>>test13out
现在输出会被追加到test13out文件,而不是创建一个新文件。
重定向文件描述符
你可以分配另外一个文件描述符给标准文件描述 符,反之亦然。这意味着你可以将STDOUT的原来位置重定向到另一个文件描述符,然后再利用 该文件描述符重定向回STDOUT。听起来可能有点复杂,但实际上相当直接。
exec 3>&1
exec 1>test14out
exec 1>&3
首先,脚本将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。这意味着任何发送给文件描述符3的输出都将出现在显示器上。
第二个exec命令将STDOUT重定向到文件,shell现在会将发送给STDOUT的输出直接重定向到 输出文件中。但是,文件描述符3仍然指向STDOUT原来的位置,也就是显示器。如果此时将输出 数据发送给文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向了。
在向STDOUT(现在指向一个文件)发送一些输出之后,脚本将STDOUT重定向到文件描述符 3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又指向了它原来的位置:显示器。
创建输入文件描述符
可以用和重定向输出文件描述符同样的办法重定向输入文件描述符。在重定向到文件之前, 先将STDIN文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将STDIN恢复到它 原来的位置。
exec 6<&0
exec 0< testfile
exec 0<&6
文件描述符6用来保存STDIN的位置。然后脚本将STDIN重定向到一个文件。 read命令的所有输入都来自重定向后的STDIN(也就是输入文件)。 在读取了所有行之后,脚本会将STDIN重定向到文件描述符6,从而将STDIN恢复到原先的位置。
创建读写文件描述符
你也可以打开单个文件描述符来作为输入和输出。可以用同 一个文件描述符对同一个文件进行读写。
不过用这种方法时,你要特别小心。由于你是对同一个文件进行数据读写,shell会维护一个 内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始。如果不够小心,它会产生一些令人瞠目的结果。看看下面这个例子。
$ cat test16
#!/bin/bash
# testing input/output file descriptor
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3
$ cat testfile
This is the first line.
This is the second line.
This is the third line.
$ ./test16
Read: This is the first line.
$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.
$
这个例子用了exec命令将文件描述符3分配给文件testfile以进行文件读写。接下来,它 通过分配好的文件描述符,使用read命令读取文件中的第一行,然后将这一行显示在STDOUT上。 最后,它用echo语句将一行数据写入由同一个文件描述符打开的文件中。
在运行脚本时,一开始还算正常。输出内容表明脚本读取了testfile文件中的第一行。但如果 你在脚本运行完毕后,查看testfile文件内容的话,你会发现写入文件中的数据覆盖了已有的数据。
当脚本向文件中写入数据时,它会从文件指针所处的位置开始。read命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符。在echo语句将数据输出到文件时,它会将数据放在文件指针的当前位置,覆盖了该位置的已有数据。
关闭文件描述符
要关闭文件描述符,将它重定向到特殊符号&-。脚本中看起来如下:
exec 3>&-
该语句会关闭文件描述符3,不再在脚本中使用它。
一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则shell会生成错误消息。
在关闭文件描述符时还要注意另一件事。如果随后你在脚本中打开了同一个输出文件,shell 会用一个新文件来替换已有文件。这意味着如果你输出数据,它就会覆盖已有文件。
列出打开的文件描述符
lsof命令会列出整个Linux系统打开的所有文件描述符。这是个有争议的功能,因为它会向 非系统管理员用户提供Linux系统的信息。鉴于此,许多Linux系统隐藏了该命令,这样用户就不 会一不小心就发现了。
在很多Linux系统中(如Fedora),lsof命令位于/usr/sbin目录。要想以普通用户账户来运行 它,必须通过全路径名来引用:
$ /usr/sbin/lsof
该命令会产生大量的输出。它会显示当前Linux系统上打开的每个文件的有关信息。这包括 后台运行的所有进程以及登录到系统的任何用户。
有大量的命令行选项和参数可以用来帮助过滤lsof的输出。最常用的有-p和-d,前者允许 指定进程ID(PID),后者允许指定要显示的文件描述符编号。
要想知道进程的当前PID,可以用特殊环境变量$$(shell会将它设为当前PID)。-a选项用来 对其他两个选项的结果执行布尔AND运算.
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
lsof的默认输出中有7 列信息
与STDIN、STDOUT和STDERR关联的文件类型是字符型。因为STDIN、STDOUT和STDERR文件描述符都指向终端,所以输出文件的名称就是终端的设备名。所有3种标准文件都支持读和写(尽管向STDIN写数据以及从STDOUT读数据看起来有点奇怪)。
阻止命令输出
有时候,你可能不想显示脚本的输出。这在将脚本作为后台进程运行时很常见(参见第16 章)。如果在运行在后台的脚本出现错误消息,shell会通过电子邮件将它们发给进程的属主。这会很麻烦,尤其是当运行会生成很多烦琐的小错误的脚本时。
要解决这个问题,可以将STDERR重定向到一个叫作null文件的特殊文件。null文件跟它的名 字很像,文件里什么都没有。shell输出到null文件的任何数据都不会保存,全部都被丢掉了。
在Linux系统上null文件的标准位置是/dev/null。你重定向到该位置的任何数据都会被丢掉,不会显示。
ls -al > /dev/null
这是避免出现错误消息,也无需保存它们的一个常用方法。
也可以在输入重定向中将/dev/null作为输入文件。由于/dev/null文件不含有任何内容,程序员 通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建。
cat /dev/null > testfile
文件testfile仍然存在系统上,但现在它是空文件。这是清除日志文件的一个常用方法,因为日志文件必须时刻准备等待应用程序操作。
记录消息
将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场。你不用将输出重定向 两次,只要用特殊的tee命令就行。
tee命令相当于管道的一个T型接头。它将从STDIN过来的数据同时发往两处。一处是 STDOUT,另一处是tee命令行所指定的文件名:
tee filename
由于tee会重定向来自STDIN的数据,你可以用它配合管道命令来重定向命令输出。
$ date | tee testfile
如果你想将数据追加到文件中,必须用-a选项。
$ date | tee -a testfile
利用这个方法,既能将数据保存在文件中,也能将数据显示在屏幕上。
重定向脚本内部的文本块
有时候,我们需要对文本块(多行文本)像标准输人一样进行重定向。考虑一个特殊情况:
源文本就位于shell脚本中。一个实用的例子是向log文件中写入头部数据,可以按照下面的方法完成:
#! /bin/baah
cat < <EOF>log.txt
LOG PILE HEADER
This is a test log file
Functions System statistics
EOF
在cat < <EOF>log.txt与下一个EOF行之间的所有文本行都会被当做stdin数据。log_txt文件的内容打印如下:
$ cat log.txt
LOG FILE HBADBR
this is a test log file
Function: System statistics
>&与&>
shell上:
0表示标准输入
1表示标准输出
2表示标准错误输出
> 默认为标准输出重定向,与 1> 相同
2>&1 意思是把 标准错误输出 重定向到 标准输出.
&>file 意思是把 标准输出 和 标准错误输出 都重定向到文件file中
用例子说话:
grep da * 1>&2
rm -f $(find / -name core) &> /dev/null
上面两例中的 & 如何理解,&不是放到后台执行吗?
解释:
- &>file或n>&m均是一个独立的重定向符号,不要分开来理解。
- 明确文件和文件描述符的区别。
- &>file表示重定向标准输出和错误到文件
- n>&m表示使文件描述符n成为输出文件描述符m的副本。当你运行某些命令的时候,出错信息也许很重要,便于你检查是哪出了毛病,如:2>&1
例如:
注意,为了方便理解,必须设置一个环境使得执行grep da *命令会有正常输出和错误输出,然后分别使用下面的命令生成三个文件:
|
|
为何2>&1要写在后面?
command > file 2>&1
首先是command > file将标准输出重定向到file中, 2>&1 是标准错误拷贝了标准输出的行为,也就是同样被重定向到file中,最终结果就是标准输出和错误都被重定向到file中。
command 2>&1 >file
2>&1 标准错误拷贝了标准输出的行为,但此时标准输出还是在终端。>file 后输出才被重定向到file,但标准错误仍然保持在终端。
文章作者 Forz
上次更新 2017-08-10