《Linux命令行与shell脚本编程大全》,4 E -- Chapter 15
一、理解输入和输出
显示脚本输出的方法:
- 在显示器屏幕上显示输出
- 将输出重定向到文件中
这两种方法要么将数据输出全部显示,要么什么都不显示。但有时将一部分数据在显示器上显示,另一部分数据保存到文件中也是不错的。
对此,了解Linux如何处理输入输出能够帮助你就能将脚本输出放到正确位置。
接下来会介绍如何用标准的Linux输入和输出系统来将脚本输出导向特定位置。
1. 标准文件描述符
Linux系统将每个对象当作文件处理。这包括输入和输出进程。Linux用文件描述符( file descriptor )来标识每个文件对象。
文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell保留了前三个文件描述符( 0 、 1 和 2 ):
表1
文件描述符 | 缩 写 | 描 述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
这三个特殊文件描述符会处理脚本的输入和输出。shell用它们将shell默认的输入和输出导向到相应的位置。
1.1 STDIN
STDIN 文件描述符代表shell的标准输入。
对终端界面来说,标准输入是键盘。shell从 STDIN文件描述符对应的键盘获得输入,在用户输入时处理每个字符。在使用输入重定向符号( < )时,Linux会用重定向指定的文件来替换标准输入文件描述符。
它会读取文件并提取数据,就如同它是键盘上键入的。
许多bash命令能接受 STDIN 的输入,尤其是没有在命令行上指定文件的话。下面是个用 cat命令处理 STDIN 输入的数据的例子。
$ cat
this is a test
this is a test
this is a second test.
this is a second test.
当在命令行上只输入 cat 命令时,它会从 STDIN 接受输入。输入一行, cat 命令就会显示出
一行。
但你也可以通过 STDIN 重定向符号强制 cat 命令接受来自另一个非 STDIN 文件的输入。
$ cat < testfile
This is the first line.
This is the second line.
This is the third line.
现在 cat 命令会用testfile文件中的行作为输入。你可以使用这种技术将数据输入到任何能从STDIN 接受数据的shell命令中。
1.2 STDOUT
STDOUT 文件描述符代表shell的标准输出。
在终端界面上,标准输出就是终端显示器。shell的所有输出(包括shell中运行的程序和脚本)会被定向到标准输出中,也就是显示器。
默认情况下,大多数bash命令会将输出导向 STDOUT 文件描述符。你可以用输出重定向来改变。
$ ls -l > test2
$ cat test2
total 20
-rw-rw-r-- 1 rich rich 53 2014-10-16 11:30 test
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 73 2014-10-16 11:23 testfile
通过输出重定向符号,通常会显示到显示器的所有输出会被shell重定向到指定的重定向文件。
你也可以将数据追加到某个文件。这可以用 >> 符号来完成。
$ who >> test2
$ cat test2
total 20
-rw-rw-r-- 1 rich rich 53 2014-10-16 11:30 test
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 73 2014-10-16 11:23 testfile
rich pts/0 2014-10-17 15:34 (192.168.1.2)
who 命令生成的输出会被追加到test2文件中已有数据的后面。
但是,如果你对脚本使用了标准输出重定向,你会遇到一个问题。下面的例子说明了可能会
出现什么情况。
$ ls -al badfile > test3
ls: cannot access badfile: No such file or directory
$ cat test3
$
当命令生成错误消息时,shell并未将错误消息重定向到输出重定向文件。shell创建了输出重定向文件,但错误消息却显示在了显示器屏幕上。
注意,在显示test3文件的内容时并没有任何错误。test3文件创建成功了,只是里面是空的。
shell对于错误消息的处理是跟普通输出是分开的。如果你创建了在后台模式下运行的shell脚本,通常你必须依赖发送到日志文件的输出消息。用这种方法的话,如果出现了错误信息,这些信息是不会出现在日志文件中的。你需要换种方法来处理。
1.3 STDERR
shell通过特殊的 STDERR 文件描述符来处理错误消息。 STDERR 文件描述符代表shell的标准错误输出。
shell或shell中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。
默认情况下, STDERR 文件描述符会和 STDOUT 文件描述符指向同样的地方(尽管分配给它们的文件描述符值不同)。也就是说,默认情况下,错误消息也会输出到显示器输出中。
但从上面的例子可以看出, STDERR 并不会随着 STDOUT 的重定向而发生改变。使用脚本时,你常常会想改变这种行为,尤其是当你希望将错误消息保存到日志文件中的时候。
2. 重定向错误
你已经知道如何用重定向符号来重定向 STDOUT 数据。重定向 STDERR 数据也没太大差别,只要在使用重定向符号时定义 STDERR 文件描述符就可以了。有几种办法实现方法。
2.1 只重定向错误
你在表1中已经看到, STDERR 文件描述符被设成 2 。可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。该值必须紧紧地放在重定向符号前,否则不会工作。
$ ls -al badfile 2> test4
$ cat test4
ls: cannot access badfile: No such file or directory
现在运行该命令,错误消息不会出现在屏幕上了。该命令生成的任何错误消息都会保存在输出文件中。
用这种方法,shell会只重定向错误消息,而非普通数据。
这里是另一个将 STDOUT 和STDERR 消息混杂在同一输出中的例子。
$ ls -al test badtest test2 2> test5
-rw-rw-r-- 1 rich rich 158 2014-10-16 11:32 test2
$ cat test5
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
ls 命令的正常 STDOUT 输出仍然会发送到默认的 STDOUT 文件描述符,也就是显示器。
由于该命令将文件描述符 2 的输出( STDERR )重定向到了一个输出文件,shell会将生成的所有错误消息直接发送到指定的重定向文件中。
2.2 重定向错误和数据
如果想重定向错误和正常输出,必须用两个重定向符号。需要在符号前面放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件。
$ ls -al test test2 test3 badtest 2> test6 1> test7
$ cat test6
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
$ cat test7
-rw-rw-r-- 1 rich rich 158 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:33 test3
shell利用 1> 符号将 ls 命令的正常输出重定向到了test7文件,而这些输出本该是进入 STDOUT的。所有本该输出到 STDERR 的错误消息通过 2> 符号被重定向到了test6文件。
可以用这种方法将脚本的正常输出和脚本生成的错误消息分离开来。这样就可以轻松地识别出错误信息,再不用在成千上万行正常输出数据中翻腾了。
另外,如果愿意,也可以将 STDERR 和 STDOUT 的输出重定向到同一个输出文件。为此bash shell
提供了特殊的重定向符号 &> 。
$ ls -al test test2 test3 badtest &> test7
$ cat test7
ls: cannot access test: No such file or directory
ls: cannot access badtest: No such file or directory
-rw-rw-r-- 1 rich rich 158 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:33 test3
当使用 &> 符时,命令生成的所有输出都会发送到同一位置,包括数据和错误。
你会注意到其中一条错误消息出现的位置和预想中的不一样。badtest文件(列出的最后一个文件)的这条错误消息出现在输出文件中的第二行。为了避免错误信息散落在输出文件中,相较于标准输出,bashshell自动赋予了错误消息更高的优先级。这样你能够集中浏览错误信息了。
二、在脚本中重定向输出
可以在脚本中用 STDOUT 和 STDERR 文件描述符以在多个位置生成输出,只要简单地重定向相应的文件描述符就行了。有两种方法来在脚本中重定向输出:
- 临时重定向行输出
- 永久重定向脚本中的所有命令
1. 临时重定向
如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到 STDERR 。你所需要做的是使用输出重定向符来将输出信息重定向到 STDERR 文件描述符。
在重定向到文件描述符时,你必须在文件描述符数字之前加一个 & :
echo "This is an error message" >&2
这行会在脚本的 STDERR 文件描述符所指向的位置显示文本,而不是通常的 STDOUT 。下面这个例子就利用了这项功能。
$ cat test8
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
这行会在脚本的 STDERR 文件描述符所指向的位置显示文本,而不是通常的 STDOUT 。下面这
个例子就利用了这项功能。
$ cat test8
#!/bin/bash
testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
$
#如果像平常一样运行这个脚本,你可能看不出什么区别。
$ ./test8
This is an error
This is normal output
记住,默认情况下,Linux会将 STDERR 导向 STDOUT 。但是,如果你在运行脚本时重定向了STDERR ,脚本中所有导向 STDERR 的文本都会被重定向。
$ ./test8 2> test9
This is normal output
$ cat test9
This is an error
$
太好了!通过 STDOUT 显示的文本显示在了屏幕上,而发送给 STDERR 的 echo 语句的文本则被重定向到了输出文件。
这个方法非常适合在脚本中生成错误消息。如果有人用了你的脚本,他们可以像上面的例子中那样轻松地通过 STDERR 文件描述符重定向错误消息。
2. 永久重定向
如果脚本中有大量数据需要重定向,那重定向每个 echo 语句就会很烦琐。取而代之,你可以用 exec 命令告诉shell在脚本执行期间重定向某个特定文件描述符。
$ cat test10
#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
exec 命令会启动一个新shell并将 STDOUT 文件描述符重定向到文件。脚本中发给 STDOUT 的所有输出会被重定向到文件。
可以在脚本执行过程中重定向 STDOUT 。
$ cat test11
#!/bin/bash
# redirecting output to different locations
exec 2>testerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>testout
echo "This output should go to the testout file"
echo "but this should go to the testerror file" >&2
$
$ ./test11
This is the start of the script
now redirecting all output to another location
$ cat testout
This output should go to the testout file
$ cat testerror
but this should go to the testerror file
这个脚本用 exec 命令来将发给 STDERR 的输出重定向到文件 testerror 。接下来,脚本用echo 语句向 STDOUT 显示了几行文本。随后再次使用 exec 命令来将 STDOUT 重定向到 testout 文件。
注意,尽管 STDOUT 被重定向了,但你仍然可以将 echo 语句的输出发给 STDERR ,在本例中还是重定向到 testerror 文件。
当你只想将脚本的部分输出重定向到其他位置时(如错误日志),这个特性用起来非常方便。
不过这样做的话,会碰到一个问题。
一旦重定向了 STDOUT 或 STDERR ,就很难再将它们重定向回原来的位置。如果你需要在重定
向中来回切换的话,有个办法可以用。15.4节将会讨论该方法以及如何在脚本中使用。
3. 在脚本中重定向输入
你可以使用与脚本中重定向 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.
第14章介绍了如何使用 read 命令读取用户在键盘上输入的数据。将 STDIN 重定向到文件后,当 read 命令试图从 STDIN 读入数据时,它会到文件去取数据,而不是键盘。
这是在脚本中从待处理的文件中读取数据的绝妙办法。Linux系统管理员的一项日常任务就是从日志文件中读取数据并处理。这是完成该任务最简单的办法。
三、在脚本中重定向输入
四、创建自己的重定向
未完