一、理解输入和输出
脚本的输出显示可以显示在显示器屏幕上也可以重定向到文件中。这两种方法要么将数据输出全部显示,要么什么都不显示。但有时将一部分数据在显示器上显示,另一部分数据保存到文件中也是不错的。下面就会介绍如何使用标准的linux输入和输出系统将脚本输出导向特定位置。
(1)标准文件描述符
linux系统将每个对象当作文件处理。这包括输入和输出进程。linux用文件描述符来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有9个文件描述符。出于特殊目的,bash shell保留了前三个文件描述符(0,1,2)。
文件描述符 | 缩写 | 描述 |
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
STDIN文件描述符代表shell的标准输入,对终端界面来说,标准输入就是键盘。
STDOUT文件描述符代表shell的标准输出,在终端界面上,标准输出就是显示器。
STRERR文件描述符处理错误消息,代表shell的标准错误输出。shell或者shell中运行的程序或者脚本出错时生成的错误信息都会发送到这个位置。默认情况下,STDERR和STDOUT指向同样的地方,即错误消息也会输出到显示器。
(2)重定向错误
(2.1)只重定向错误
选择只重定向错误消息,将文件描述符值放在重定向符号前即可。文件描述符值必须紧紧地放在重定向符号前,否则不会工作。
案例:
ls 一个没有的文件,将错误消息重定向到文件test
[root@locahost]# ls -al test.txt 2> test
[root@locahost]# cat test
ls: cannot access test.txt: No such file or directory
(2.2)重定向错误和数据
如果想重定向错误和正常输出,必须用两个重定向符号。需要在符号前面放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件。
案例:将ls命令的正常输出重定向到test1文件,错误消息重定向到test3文件。
ls -al test.txt test1 2> test2 1> test3
如果想要把STDERR和STDOUT的输出重定向到同一个输出文件,可以使用特殊的重定向符号(&>)。
ls -al test.txt test1 &> test
上面的案例使用了&>符号,会把ls命令的所有输出都发送到同一位置,包括数据和错误。
二、在脚本中重定向输出
有两种方法在脚本中重定向输出:第一,临时重定向行输出;第二,永久重定向脚本中的所有命令。
(1)临时重定向
如果想在脚本中生成错误信息,可以将单独的一行输出重定向到STDERR。所需要做的就是使用输出重定向符来将输出信息重定向到STDERR文件描述符。在重定向到文件描述符时,必须在文件描述符数字前加&。
cat test.sh
#!/bin/bash
echo "this is an error" >&2
echo "this is normal output"
如果直接运行脚本test.sh,可能看不出什么区别。
[root@~]# ./test.sh
this is an error
this is normal output
默认情况下,linux会将STDERR导向STROUT。但是,如果你在运行脚本时重定向了STDERR,脚本中所有导向STDERR的文本都会被重定向。
[root@~]# ./test.sh 2> test
this is normal output
[root@~]# cat test
this is an error
可以看出通过STDOUT显示的文本显示在了屏幕上,发送给STDERR的echo语句的文本被重定向到了test输出文件中。这个方法非常适合在脚本中生成错误消息。
(2)永久重定向
如果脚本中有大量数据需要重定向,那重定向每个echo语句会非常繁琐,我们可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。
cat test.sh
#!/bin/bash
exec 1>test
echo "this is a test"
echo "this is a test output"
脚本执行的输出结果:
[root@~]# sh test.sh
[root@~]# cat test
this is a test
this is a test output
exec命令会启动一个新shell并将STDOUT文件描述符重定向到文件,脚本中发送给STDOUT的所有输出会被重定向到test文件。
三、在脚本中重定向输入
exec命令允许将STDIN重定向到linux系统上的文件中:
exec 0< testfile
这个命令会告诉shell它应该从文件testfile中获得输入,而不是STDIN。这个重定向只要在脚本需要输入时就会起作用。
cat test.sh
#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count:$line"
count=$[ $count+1 ]
done
testfile文件的内容如下:
this is the first line.
this is the second line.
this is the third line.
脚本的执行结果:
[root@~]# ./test.sh
Line #1:this is the first line.
Line #2:this is the second line.
Line #3:this is the third line.
四、创建自己的重定向
(1)创建输出文件描述符
可以用exec命令给输出分配文件描述符,和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到重新分配。
cat test.sh
#!/bin/bash
exec 3> testout
echo "this is a test"
echo "this should be stored in the file" >&3
echo "this should stored on the monitor"
脚本执行结果
[root@~]# sh test.sh
this is a test
this should stored on the monitor
[root@~]# cat testout
this should be stored in the file
这个脚本中用exec命令将文件描述符3重定向到另一个文件testout。当脚本执行echo语句时,输出内容会像预想中那样显示在STDOUT,但重定向到文件描述符3的echo语句的输出会进入另一个文件。
五、重定向文件描述符
可以分配另外一个文件描述符给标准文件描述符,反之亦然。这意味着可以将STDOUT的原来位置重定向到另一个文件描述符,然后在利用该文件描述符重定向回STDOUT。
cat test.sh
#!/bin/bash
exec 3>&1
exec 1> testout
echo "this is a test"
exec 1>&3
echo "this is a another test"
上述脚本执行结果如下:
[root@~]# chmod u+x test.sh
[root@~]# ./test.sh
this is a another test
[root@~]# ll
total 8
-rw-r--r-- 1 root root 15 Feb 9 13:44 testout
-rwxr--r-- 1 root root 104 Feb 9 13:44 test.sh
[root@~]# cat testout
this is a test
脚本中首先将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。这意味着任何发送给文件描述符3的输出都将会出现在显示器上。第二个exec命令将STDOUT重定向到文件testout。shell现在会将发送给STDOUT的输出直接重定向到输出我呢见testout中。但是文件描述符3仍然指向STDOUT原来的位置,即显示器。如果此时将输出数据发送给文件描述符3,他仍然会显示在显示器上。尽管STDOUT已经被重定向了。在向STDOUT发送一些输出之后,脚本将STDOUT重定向到文件描述符3,即显示器。这意味着STDOUT指向了原来的位置,显示器。
(2)创建输入文件描述符
在重定向到文件之前,先将STDIN文件描述符保存到另一个文件描述符,然后在读取完文件之后再将STDIN恢复他原来的位置。
cat test.sh
#!/bin/bash
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$count:$line"
count=$[ $count+1 ]
done
exec 0<&6
read -p "are you done now? " answer
case $answer in
Y|y) echo "Good Bye";;
N|n) echo "Sorry";;
esac
执行结果如下:
[root@~]# ./test.sh
Line #1:this is the first line.
Line #2:this is the second line.
are you done now? y
Good Bye
在这个案例中,文件描述符6用来保存STDIN的位置,然后脚本将STDIN重定向到一个文件testfile。read命令的所有输入都来自重定向后的STDIN(也就是输入文件)。在读取了所有行之后,脚本将STDIN重定向到文件描述符6,从而将STDIN恢复到原先的位置。脚本用了另外的一个read命令来测试STDIN是否恢复正常了,这次它等待键盘的输入。
(3)关闭文件描述符
如果你创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们。在某些情况先,需要在脚本结束前手动关闭。要关闭文件描述符,将它重定向到特殊符号&-。
比如关闭文件描述符3,可以使用下面的命令
exec 3>&-
六、记录消息
将输出同时发送到显示器和日志文件,不需要重定向2此,只要使用命令tee即可。
tee命令相当于管道的一个T型接头,它将从STDIN过来的数据同时发往两处,一处是STDOUT,一处是tee命令所指定的文件名。
tee filename
由于tee会重定向来自STDIN的数据,可以用它配合管道命令来重定向输出。
[root@~]# date|tee testfile
Sun Feb 9 14:11:10 CST 2020
[root@~]# cat testfile
Sun Feb 9 14:11:10 CST 2020
默认情况下,tee命令会在每次使用时覆盖输出文件内容。如果想追加到文件中,可以使用-a选项。
[root@~]# who |tee -a testfile
root pts/0 2020-02-03 12:12 (10.12.28.29)
[root@~]# cat testfile
Sun Feb 9 14:11:10 CST 2020
root pts/0 2020-02-03 12:12 (10.12.28.29)
利用这种方法,既能将数据显示在屏幕上,也能将数据保存在文件中。
七、知识点补充
脚本中经常会有这样的用法,>/dev/null 2>&1。这条命令其实分为两命令,一个是>/dev/null
,另一个是2>&1
。
>/dev/null
这条命令的作用是将标准输出1重定向到/dev/null
中。 /dev/null
代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null
之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。
2>&1
这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。
linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1
的作用就是让标准输出重定向到/dev/null
中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null
中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中。
>/dev/null 2>&1 VS 2>&1 >/dev/null
乍眼看这两条命令貌似是等同的,但其实大为不同。刚才提到了,linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令。那么我们同样从左到右地来分析2>&1 >/dev/null
:
2>&1
,将错误输出绑定到标准输出上。由于此时的标准输出是默认值,也就是输出到屏幕,所以错误输出会输出到屏幕。>/dev/null
,将标准输出1重定向到/dev/null
中。
我们用一个表格来更好地说明这两条命令的区别:
命令 | 标准输出 | 错误输出 |
>/dev/null 2>&1 | 丢弃 | 丢弃 |
2>&1 >/dev/null | 丢弃 | 屏幕 |