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的正文内容有空格缩进,则无效。