Shell的I/O语法比较复杂,难以理解,更难以正确使用。Shell的输入输出语法有两个难点:重定向和文件描述符。我以前对Shell脚本输入/输出的理解也有错误。最近重新整理思路,决定写一篇文章来总结I/O重定向。
一、先介绍几个基本概念:
(1)文件(File):在Unix/Linux中,文件类型有七种,这七种类型是:目录、符号链接(指向另一个文件)、套接字文件、块设备文件、字符设备文件、命名管道文件、普通文件。这意味着,I/O设备被Unix/Linux视为“特殊”的文件,所以对设备进行输入输出等同于读写文件。
(2)文件描述符(File Descriptor ):实际上,Shell中的文件描述符等同于C语言中的文件结构(参见《The C Programming Languag》的7.5节),它是一个包含文件信息的结构,这些信息包括:缓冲区的位置,缓冲区中当前字符的位置,文件是否到达末尾等。Shell脚本通过文件描述符来读写文件。一个Shell脚本有10个文件描述符。其中,0、1、2是标准输入、标准输出和标准错误,我们可以任意使用的文件描述符是3 到9。
(3)重定向(Redirect):在Shell中,我们可以对命令(命令就是程序或脚本)的标准输入、标准输出和标准错误进行重定向,使其指向其他文件。
(4)重定向的作用域(Redirect's Scope ):重定向的“作用域”这个概念是我自己提出的,它对理解重定向的作用效果非常重要!我们来看下面这几个例子:
例1:重定向的“作用域”局限于一个命令
#!/bin/bash
echo "hello world" > result.txt
解析:这个重定向的作用域是echo命令,命令结束重定向也就结束了。
二、下面开始讨论Shell脚本中输入/输出的的两种情况:
(1)脚本只使用标准输入、标准输出和标准错误
Shell会自动为我们打开和关闭0、1、2这三个文件描述符,我们不需要显式地打开或关闭它们。标准输入是命令的输入,默认指向键盘;标准输出是命令的输出,默认指向屏幕;标准错误是命令错误信息的输出,默认指向屏幕。
如果没有显式地进行重定向,命令通过文件描述符0从屏幕读取输入,通过文件描述符1和2将输出和错误信息输出到屏幕。但如果我们想从其他文件(再次强调,I/O设备在Unix/Linux中也是文件)读取输入或产生输出,就需要对0、1、2使用重定向了。其语法如下:
command < filename 把标准输入重定向到filename文件中
command 0< filename 把标准输入重定向到filename文件中
command > filename 把标准输出重定向到filename文件中(覆盖)
command 1> fielname 把标准输出重定向到filename文件中(覆盖)
command >> filename 把标准输出重定向到filename文件中(追加)
command 1>> filename 把标准输出重定向到filename文件中(追加)
command 2> filename 把标准错误重定向到filename文件中(覆盖)
command 2>> filename 把标准输出重定向到filename文件中(追加)
command > filename 2>&1 把标准输出和标准错误一起重定向到filename文件中(覆盖)
command >> filename 2>&1 把标准输出和标准错误一起重定向到filename文件中(追加)
command < filename >filename2 把标准输入重定向到filename文件中,把标准输出重定向
到filename2文件中
command 0< filename 1> filename2 把标准输入重定向到filename文件中,把标准输出重定向
到filename2文件中
重定向的使用有如下规律:
1)标准输入0、输出1、错误2需要分别重定向,一个重定向只能改变它们中的一个。
2)标准输入0和标准输出1可以省略。(当其出现重定向符号左侧时)
3)文件描述符在重定向符号左侧时直接写即可,在右侧时前面加&。
4)文件描述符与重定向符号之间不能有空格!
(2)脚本使用外部文件
当Shell脚本使用外部文件而不是默认的I/O设备作为输入输出时,我们“可以”使用3到9等几个文件描述符显式地打开或关闭外部文件。它的作用域是从显式打开文件到显式关闭文件之间。之所以说可以而不是必需,是因为我们可以重定向标准输入、输出和错误到外部文件来达到同样的目的。
使用文件描述符显式打开文件需要使用内置命令exec,格式如下:
exec 文件描述符(没有空格)重定向符号文件
使用文件描述符显式关闭文件,格式如下:
exec 文件描述符(没有空格)<&-
例2:使用外部文件
#!/bin/bash
exec 3< name.txt
while read line <&3
do
echo "$line"
done
exec 3<&-
解析:这个例子是从例2修改得来的,可以做比较。显式打开外部文件后,在循环执行期间,3都被重定向到了当前目录的的name.txt文件,即每次 read都是从文件中读取一行,且read是顺序读取文件的所有行。while循环结束时,重定向没有结束。直到显式文件为止。