1. 理解数据输入和输出
两种显示脚本输出的方法:
- 在显示器屏幕上显示输出
- 将输出数据重定向到文件中
1.1. 标准文件描述符
linux 系统将每个对象当作文件处理。linux系统中万物皆是文件,这包括输入和输出进程。Linux 用文件描述符(filedescriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以含有九个文件描述符。bash shell 自己保留了九个文件描述符中的前三个文件描述符( 0 ,1 和 2 ),其余的文件描述符交由用户自己定义。
linux的标准文件描述符
---------------------------
文件描述符 | 缩 写 描 述
---------------------------
0 STDIN | 标准输入
1 STDOUT | 标准输出
2 STDERR | 标准错误
---------------------------
STDIN: 文件描述符代表 shell 的标准输入。对终端界面来说,标准输入是键盘。shell 从
STDIN
文件描述符对应的键盘获得输入,在用户输入时处理每个字符。在使用输入重定向符号<
时,linux 会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是从键盘键入的。
STDOUT: 文件描述符代表shell的标准输出。在终端界面上,标准输出就是终端显示器。shell 的所有输出(包括shell中运行的程序和脚本)会被定向到标准输出中,标准输出也就是显示器。
STDERR: shell 通过特殊的
STDERR
文件描述符来处理错误消息。STDERR
文件描述符代表 shell 的标准错误输出。shell 或 shell 中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。默认情况下,STDERR
文件描述符会和STDOUT
文件描述符指向同样的地方(尽管分配给它们的文件描述符值同)。即默认情况下,错误消息也会输出到显示器输出中。
1.2. 重定向符号
<
重定向输入符号
>
重定向输出符号
1.3. 重定向数据和错误
默认的
>
和1>
是相同的,表示重定向数据输出,而不是错误。
1> 文件名称
重定向数据输出位置
2> 文件名称
重定向错误输出位置
&> 文件名称
同时重定向数据和错误输出位置
1.4. 重定向文件描述符
除了数据和错误提示可以重定向之外,文件描述符也可以进行重定向。
>&文件描述符
加上&
表示这是一个文件描述符名称,而不是一个文件名称。如果遗漏了&
符号,那么 shell 会认为这个是一个文件名称,命令就变成> 文件名称
即变成与1> 文件名称
意思相同。
2. 在脚本中重定向输出
可以在脚本中用
STDOUT
和STDERR
文件描述符以在多个位置生成输出,只要简单地重定向相应的文件描述符就行了。有两种方法来在脚本中重定向输出:
- 临时重定向行输出
- 永久重定向脚本中的所有命令
2.1. 临时重定向输出
如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到
STDERR
。你所需要做的是使用输出重定向符来将输出信息重定向到STDERR
文件描述符。就像这样echo "This is an error message" >&2
这里将标准输出文件描述符重定向到错误输出文件描述符。
2.2. 永久重定向输出
如果脚本中有大量数据需要重定向,那重定向每个 echo 语句就会很烦琐。取而代之,你可以用
exec
命令告诉 shell 在脚本执行期间重定向某个特定文件描述符
。运行时本来由该文件描述符输出的内容会变成由重定向到的那个文件描述符输出,内容并由这个文件描述符指定的位置接收。就像这样:exec 1>filename
。exec
命令会启动一个新 shell 并将STDOUT
(即描述符1
) 文件描述符重定向到指定文件。
3. 在脚本中重定向输入
可以使用与脚本中重定向
STDOUT
和STDERR
相同的方法来将STDIN
从键盘
重定向到其他位置。exec
命令允许你将STDIN
重定向到Linux系统上的文件中就像这样:exec 0< filename
。这个命令会告诉 shell 它应该从文件 filename 中获得输入,而不是 STDIN 。这个输入重定向只要在脚本需要输入时就会作用。
4. 创建自定义的重定向
我们已经知道每个进程一次最多可以含有九个文件描述符,bash shell 自己保留了九个文件描述符中的前三个文件描述符( 0 ,1 和 2 )。那么还剩下 3 - 8 的文件描述符均可用作输入或输出的自定义重定向。可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。
4.1. 创建输出文件描述符
可以用
exec
命令来给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到重新分配。
exec 3>filename
echo "and this should be stored in the file" >&3
脚本用
exec
命令将文件描述符3
重定向到另一个文件。当脚本执行 echo 语句时,输出内容会像预想中那样显示在标准输出STDOUT
上。但重定向到文件描述符3
的那行 echo 语句的输出内容却进入了另一个文件。
如果文件已经存在,还可以追加文件
exec >> filename
输出会被追加到 filename 文件,而不会创建一个新文件。
4.2. 重定向文件描述符
恢复已重定向的文件描述符。你可以分配另外一个文件描述符给标准文件描述符,反之亦然。这意味着你可以将
STDOUT
的原来位置重定向到另一个文件描述符
,然后再利用该文件描述符
重定向回STDOUT
。这句话可以用一个图示就是:1 > &3 需要恢复到重定向之前时 3 > &1
。
exec 3>&1 // 将文件描述符 3 重定向到文件描述符 1,意味着任何发送给文件描述符 3 的输出都通过文件描述符 1 输出,即内容将出现在显示器上。
exec 1>filename // 将文件描述符 1 的输出内容重定向到 filename 这个文件。但是文件描述符 3 依然指向的是文件描述符 1,所以文件描述符 1 的内容依旧会输出到显示器。所以起到了一个输出分流的作用。
下面
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
echo "Now things should be back to normal"
4.3. 创建输入文件描述符
可以用和重定向输出文件描述符同样的办法重定向输入文件描述符。在重定向到文件之前,先将
STDIN
文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将STDIN
恢复到它原来的位置。
exec 6<&0 // 将文件描述符 0 重定向到 6
exec 0< filename // 将文件描述符 0 重定向输出到文件
exec 0<&6 // 这样将文件描述符切换回原来的文件描述符
文件描述符
6
用来保存STDIN
的位置。然后脚本将 STDIN 重定向到一个文件。read
命令的所有输入都来自重定向后的STDIN
(也就是输入文件)。
4.4. 创建读写文件描述符
读写文件描述符可以用同一个文件描述符对同一个文件进行读写。
4.5. 关闭文件描述符
如果你创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们。然而在有些情况下,你需要在脚本结束前手动关闭文件描述符。要关闭文件描述符,将它重定向到特殊符号
&-
。
exec 3> filename
echo "This is a test line of data" >&3
exec 3>&-
一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则shell会生成错误消息。
5. 列出打开的文件描述符
有时要记住哪个文件描述符被重定向到了哪里很难。为了帮助你理清条理,bash shell提供了 lsof 命令。
但是存在一个问题,lsof
命令会列出整个 linux 系统打开的所有文件描述符。这是个有争议的功能,因为它会向非系统管理员用户提供 linux 系统的信息。鉴于此,许多 linux 系统隐藏了该命令,这样用户就不会一不小心就发现了。在很多 linux 系统中(如Fedora),lsof
命令位于/usr/sbin
目录。要想以普通用户账户来运行它,必须通过全路径名来引用,像这样:/usr/sbin/lsof
该命令会产生大量的输出。它会显示当前Linux系统上打开的每个文件的有关信息。这包括后台运行的所有进程以及登录到系统的任何用户。
有大量的命令行选项和参数可以用来帮助过滤 lsof 的输出。最常用的有
-p
和-d
,前者允许指定进程ID(PID),后者允许指定要显示的文件描述符编号。
要想知道进程的当前 PID,可以用特殊环境变量
$$
(shell会将它设为当前PID)。-a
选项用来对其他两个选项的结果执行布尔AND
运算。
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0
lsof 的默认输出
----------------------------------------------------------------------------------
列 | 描 述
----------------------------------------------------------------------------------
COMMAND | 正在运行的命令名的前9个字符
PID | 进程的PID
USER | 进程属主的登录名
FD | 文件描述符号以及访问类型( r 代表读, w 代表写, u 代表读写)
TYPE | 文件的类型( CHR 代表字符型, BLK 代表块型, DIR 代表目录, REG 代表常规文件)
DEVICE | 设备的设备号(主设备号和从设备号)
SIZE | 如果有的话,表示文件的大小
NODE | 本地文件的节点号
NAME | 文件名
-----------------------------------------------------------------------------------
6. 阻止命令输出
有时候你希望不输出任何错误信息,可以将
STDERR
重定向到一个叫作 null 文件的特殊文件。null 文件跟它的名字很像,文件里什么都没有。shell 输出到 null 文件的任何数据都不会保存,全部都被丢掉了。linux系统上 null 文件的标准位置是/dev/null
。使用时像这样ls -al > /dev/null
。
7. 创建临时文件
Linux 系统有特殊的目录,专供临时文件使用。Linux 使用
/tmp
目录来存放不需要永久保留的文件。大多数 Linux 发行版配置了系统在启动时自动删除/tmp
目录的所有文件。系统上的任何用户账户都有权限在读写/tmp
目录中的文件。这个特性为你提供了一种创建临时文件的简单方法,而且还不用操心清理工作。
mktemp
命令可以在/tmp
目录中创建一个唯一的临时文件。shell 会创建这个文件,但不用默认的 umask 值(参见第7章)。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然,root用户除外)。
7.1 创建本地临时文件
默认情况下,
mktemp
会在本地目录中创建一个文件。要用mktemp
命令在本地目录中创建一个临时文件,你只要指定一个文件名模板就行了。模板可以包含任意文本文件名,在文件名末尾加上6个 X 就行了。就像这样:mktemp filename.XXXXXX
,mktemp
命令会用6个字符码替换这6个 X,比如这样的testing.1DRLuV
,从而保证文件名在目录中是唯一的。你可以创建多个临时文件,它可以保证每个文件都是唯一的。
7.2 在/tmp 目录创建临时文件
-t
选项会强制mktemp
命令来在系统的临时目录来创建该文件。在用这个特性时,mktemp
命令会返回用来创建临时文件的全路径,而不是只有文件名。mktemp -t test.XXXXXX
得到这样的全路径名称/tmp/test.xG3374
由于 mktemp 命令返回了全路径名,你可以在Linux系统上的任何目录下引用该临时文件,不管临时目录在哪里。就像这样进行引用tempfile=$(mktemp -t tmp.XXXXXX)
7.3 创建临时目录
-d
选项告诉mktemp
命令来创建一个临时目录而不是临时文件。可以像下面这样进行创建和引用。
tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
这段脚本在当前目录创建了一个目录,然后它用
cd
命令进入该目录
8. 记录消息
将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场。你不用将输出重定向两次,只要用特殊的
tee
命令就行。
tee
命令相当于管道的一个 T 型接头。它将从STDIN
过来的数据同时发往两处。一处是STDOUT
,另一处是tee
命令行所指定的文件名:tee filename
由于
tee
会重定向来自STDIN
的数据,你可以用它配合管道命令来重定向命令输出。
$ date | tee testfile
Sun Oct 19 18:56:21 EDT 2014
输出出现在了
STDOUT
中,同时也写入了指定的文件中。利用这个方法,既能将数据保存在文件中,也能将数据显示在屏幕上。注意默认情况下,tee
命令会在每次使用时覆盖输出文件内容。如果你想将数据追加到文件中,必须用-a
选项。
$ date | tee -a testfile
Sun Oct 19 18:58:05 EDT 2014