Linux Shell核心编程指南
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.4 输入与输出的重定向

在大多数系统中,一般会默认把输出信息显示在屏幕上,而标准的输入信息则通过键盘获取。但在编写脚本时,当有些命令的输出信息我们不能或不希望显示在屏幕上(脚本执行时,大量的输出信息反而会让用户感到迷茫)。此时,不如先把输出的信息暂时写入文件中,后期需要时,再读取文件,提取需要的信息。对于默认的标准输入信息也会有类似的问题,在Linux系统中当我们使用mail命令发送邮件时,程序需要读取邮件的正文,默认通过读取键盘的输入数据作为正文,这样会让脚本进入交互模式,因为读取键盘信息是需要人为手动输入的。此时,如果能改变默认的输入方式,不再从键盘读取数据,而是从提前准备好的文件中读取数据,就可以让mail程序在需要时自动读取文件内容,自动发送邮件,而不需要人为的手动交互。这样脚本的自动化效果会更好。

在Linux系统中输出可以分为标准输出和标准错误输出。标准输出的文件描述符为1,标准错误输出的文件描述符为2。而标准输入的文件描述符则为0。

如果希望改变输出信息的方向,可以使用>或>>符号将输出信息重定向到文件中。使用1>或1>>可以将标准输出信息重定向到文件(1可以忽略不写,默认值就是1),也可以使用2>或2>>将错误的输出信息重定向到文件。这里使用>符号将输出信息重定向到文件,如果文件不存在,则系统会自动创建该文件,如果文件已经存在,则系统会将该文件的所有内容覆盖(原有数据会丢失!)。而使用>>符号将输出信息重定向到文件,如果文件不存在,则系统会自动创建该文件,如果文件已经存在,则系统会将输出的信息追加到该文件原有信息的末尾。

下面的例子中,echo命令本来会将数据输出显示在屏幕上,但如果使用重定向后就可以将输出的信息导出到文件中。

#创建新文件,导出数据到文件
[root@centos7~]# echo "hello the world" > test.txt
[root@centos7~]# cat test.txt
hello the world
#覆盖重定向,前面的数据丢失
[root@centos7~]# echo "Jacob Shell Scripts" > test.txt
[root@centos7~]# cat test.txt
Jacob Shell Scripts
[root@centos7~]# echo "test file" >> test.txt        #追加重定向,数据不丢失
[root@centos7~]# cat test.txt
Jacob Shell Scripts
test file

前面的echo命令不会出现报错信息。但使用ls命令时,根据文件是否存在,最后的输出信息又分为标准输出和错误输出。此时,如果我们仅使用>或>>,则无法将错误信息重定向导出到一个文件。

[root@centos7~]# ls /etc/hosts
/etc/hosts                                          #标准输出
[root@centos7~]# ls /nofiles
ls: cannot access /nofiles: No such file or directory      #错误输出
[root@centos7~]# ls -l /etc/hosts > test.txt      #将标准输出重定向到文件
[root@centos7~]# cat test.txt                     #成功地将数据重定向到文件
-rw-r--r--. 1 root root 158 Jun  7  2013 /etc/hosts
[root@centos7~]# ls -l /nofiles > test.txt        #将标准输出重定向
ls: cannot access /nofiles: No such file or directory#错误信息依然显示在屏幕上

这里我们就需要使用2>或2>>来实现错误输出的重定向。

[root@centos7~]# ls -l /nofiles 2> test.txt       #错误重定向,覆盖数据
[root@centos7~]# cat test.txt                     #将标准输出重定向到文件
ls: cannot access /nofiles: No such file or directory      #原始数据全部丢失
[root@centos7~]# ls -l /oops 2>> test.txt         #错误重定向,追加数据
[root@centos7~]# cat test.txt                     #之前的数据不会丢失
ls: cannot access /nofiles: No such file or directory
ls: cannot access /oops: No such file or directory

如果一条命令既有标准输出(正确输出),又有错误输出,该如何重定向呢?

[root@centos7~]# ls -l /etc/hosts /nofile > test.txt      #仅重定向标准输出
ls: cannot access /nofile: No such file or directory        #错误输出未重定向
[root@centos7~]# ls -l /etc/hosts /nofile 2> test.txt     #仅重定向错误输出
-rw-r--r--. 1 root root 158 Jun  7  2013 /etc/hosts         #标准输出未重定向

其实,我们可以将标准输出和错误输出分别重定向到不同的文件,也可以同时将它们重定向到相同的文件。

#分别重定向到不同的文件
[root@centos7~]# ls -l /etc/hosts /nofile > ok.txt 2> error.txt
[root@centos7~]# cat ok.txt
-rw-r--r--. 1 root root 158 Jun  7  2013 /etc/hosts
[root@centos7~]# cat error.txt
ls: cannot access /nofile: No such file or directory

使用&>符号可以同时将标准输出和错误输出都重定向到一个文件(覆盖),也可以使用&>>符号实现追加重定向。

[root@centos7~]# ls -l /etc/hosts /nofile &> test.txt
[root@centos7~]# cat test.txt
ls: cannot access /nofile: No such file or directory
-rw-r--r--. 1 root root 158 Jun  7  2013 /etc/hosts
[root@centos7~]# ls -l /etc/passwd /ooops &>> test.txt
[root@centos7~]# cat test.txt
ls: cannot access /nofile: No such file or directory
-rw-r--r--. 1 root root 158 Jun  7  2013 /etc/hosts
ls: cannot access /ooops: No such file or directory
-rw-r--r--. 1 root root 2170 Sep 20 21:06 /etc/passwd

最后,我们还可以使用2>&1将错误输出重定向到标准正确输出,也可以使用1>&2将标准正确输出重定向到错误输出。

下面的命令虽然都在屏幕上显示了结果。第一条命令虽然是报错信息,却是从标准正确的通道显示在屏幕上的。而第二条命令虽然原本没有错误信息,但通过将正确信息重定向到错误输出,最后的hello是通过错误输出的通道显示在屏幕上的。

[root@centos7~]# ls /nofile 2>&1
ls: cannot access /nofile: No such file or directory
[root@centos7~]# echo "hello" 1>&2
Hello

图1-3是ls命令对比。正常情况下,因为系统没有/nofile文件,所以ls命令会报错,报错信息会通过错误输出的通道传递给显示器。但当我们使用2>&1命令时,就会把错误信息重定向到标准正确输出,虽然屏幕最终也会显示报错信息,却是通过标准输出通道传递给显示器的。

图1-3 ls命令对比

图1-4是echo命令对比。正常情况下,echo命令会通过标准输出将消息显示在屏幕上。而当我们使用1>&2时,系统就会把正确的输出信息重定向到错误输出,虽然屏幕上最终也显示了hello,却是通过错误输出通道传递给显示器的。

图1-4 echo命令对比

[root@centos7~]# ls /etc/passwd /nofile >test.txt 2>&1
[root@centos7~]# cat test.txt
ls: cannot access /nofile: No such file or directory
/etc/passwd

结合这种特殊的重定向方式,我们还可以将标准输出重定向到文件,然后将错误输出重定向到标准正确输出。最终把正确的和错误的信息都导入文件中,如图1-5所示。

图1-5 标准输出与错误输出

Linux系统中有一个特殊的设备/dev/null,这是一个黑洞。无论往该文件中写入多少数据,都会被系统吞噬、丢弃。如果有些输出信息是我们不再需要的,则可以使用重定向将输出信息导入该设备文件中。注意:数据一旦导入黑洞将无法找回。

[root@centos7~]# echo "hello" > /dev/null
[root@centos7~]# ls /nofile 2>/dev/null
[root@centos7~]# ls /etc/hosts /nofile &>/dev/null

除了可以对输出进行重定向,还可以对输入进行重定向。默认标准输入为键盘鼠标。但键盘需要人为的交互才可以完成输入。比如下面的mail命令,执行完命令后程序就会进入等待用户输入邮件内容的状态,只要用户不输入内容,并使用独立的一行点表示邮件内容结束,mail程序就会一直停留在该状态。

[root@centos7~]#mail-s warning root@localhost  #-s设置邮件标题,收件人为root
this is a test mail
.
EOT

以上所有邮件正文都需要人工手动输入,而未来当我们需要使用脚本自动发送邮件时,这就存在问题。为了解决这个问题,我们可以使用<符号进行输入重定向。<符号后面需要跟一个文件名,这样可以让程序不再从键盘读取输入数据,而从文件中读取数据。

[root@centos7~]# mail -s warning root@localhosts < /etc/hosts  #非交互发
送邮件

如果我们希望自动非交互地发送邮件,而又没有提前准备文件,可以吗?

可以使用<<符号实现相同的效果。这样脚本就不需要依赖邮件内容的文件即可独立运行。使用<<符号可以将数据内容重定向传递给前面的一个命令,作为命令的输入。

<<符号(也被称为Here Document)代表你需要的内容在这里。下面看一个cat通过Here Document读取数据,再通过输出重定向将数据导出到文件的例子。

在Linux系统中经常会使用fdisk命令对磁盘进行分区,但该命令是交互式的,而我们现在需要编写脚本实现自动分区、自动格式化、自动挂载分区等操作。针对这种问题,也可以通过Here Document来解决。下面我们来编写一个这样的自动分区脚本。

警告

该脚本会删除磁盘中的所有数据,全部数据都将丢失!

在编写脚本时为了提高代码的可读性,往往需要在代码中添加额外的缩进。然而,使用<<将数据导入程序时,如果内容里面有缩进,则连同缩进的内容都会传递给程序。而此时的Tab键仅仅起缩进的作用,我们并不希望传递给程序。如果需要,可以使用<<-符号重定向输入的方式实现,这样系统会忽略掉所有数据内容及分隔符(EOF)前面的Tab键。使用这种方式仅可以忽略Tab键,如果Here Document的正文内容有空格缩进,则无效。