理解输入与输出
脚本的两种显示方法就是:第一,在显示器屏幕上显示输出;第二,将输出重定向到文件。这篇笔记主要是理解Linux如何处理输入与输出的。
标准文件描述符
Linux系统将每个对象当作文件处理,这就包括了输入和输出过程,Linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件,每个进程最多可以有9个文件描述符,而bash shell保留了前3个文件描述符,即0、1和2。
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
这三个特殊文件描述符会处理脚本中的输入和输出。shell用它们将shell默认的输入和输出导向到相应的位置。
STDIN
STDIN文件描述符代表了shell的标准输入,对于终端来说,标准输入就是键盘,shell从STDIN文件描述符对应的键盘获得输入,在用户输入时处理每个字符。在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是键盘上的键输入的。许多bash命令能接受STDIN的输入,尤其是在命令行上指定文件的话。下面看一下cat
这个案例:
|
|
当在命令行上只输入cat
时,它会从STDIN
接受僌,输入一行,cat
命令就会显示出一行。但也可以通过STDIN
重定向符号强制cat
命令接受来自另一个非STDIN
文件的输入,如下所示:
|
|
这个结果显示,cat命令会用testfile文件中的行作为输入。
STDOUT
STDOUT
文件描述符代表shell的标准输出。在终端界面上,标准输出应时终显示器。Shell的所有输出(包括shell文中运行的程序和脚本)会被定向到标准输出中,也就是显示器。默认情况下,大多数bash命令会将输出导向STDOUT文件描述符,也可以使用输出重定向来改变,如下所示:
|
|
使用>>
符号可以向一个文件中追加数据,如下所示:
|
|
当某个主文件不存在时,无产进行重定向,但会生成要重定向的文件,如下所示:
|
|
由于不存在badfile文件,因此bash无法获取里面的信息,也无法将其信息重定向到test3,但是test3这个文件会被创建,只是里面是空的。
STDERR
STDERR
文件描述符被设成2,可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。该值必须紧紧地放在重定向符号前,否则不会工作。如下所示;
|
|
此时,错误消息不会出现在屏幕上,该命令生怕任何错误消息都会保存在输出文件中,用这种方法,shell只重定向错误消息,而非普通数据。下面的一个案例是将STDOUT
和STDERR
消息混杂在同一个输出中的例子,如下所示;
|
|
ls命令的正常STDOUT
输出仍然公改善到默认的STDOUT
文件描述符,也就是显示器,由于该命令将文件描述符2的输出(STDERR
)重定向到了一个输出文件,shell将生成的所有错误消息直接改善到指定的重定向文件中。
重定向错误和数据
如果想重定向错误和正常输出,需要用2个重定向符号,需要在符号前放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件,如下所示:
|
|
在这个案例中,shell利用1>
符号将ls命令的正常输出重定向到了test7
文件,而这些输出本该是进入STDOUT
的。所有本该输出到STDERR
的错误消息通过2>
符号被重定向到了test6
文件。 这种方法哦可以将脚本的正常输出和脚本的错误消息分离开来。
除此之外,还可以将STDERR
和STDOUT
的输出重定向到同一个输出文件,可以使用特殊的重定向符号$>
,如下所示::
|
|
当使用&>
符号哩主,命令生成的所有输出都会改善到同一位置,包括数据和错误。bash shell会自动赋予错误消息更高的优先级,在输出文件的最开头部分显示错误消息。
在脚本中重定向输出
有2种方法在脚本中重定向输出,第一,临时重定向行输出;第二,永久重定向脚本中的所有命令。
临时重定向
如果脚本中意外生成错误消息,可以将单独的一行输出重定向到STDERR
,在重定向到文件描述符时,需要在文件描述符前面添加一具&
,如下所示:
|
|
运行过程如下所示:
|
|
从结果可以看出,STDOUT显示的文件会出现在屏幕上,而发送给STDERR的echo语句的文本则被重定向到了输出文件。这个方便适合在脚本中生成错误的消息。
永久重定向
如果脚本中有大量数据需要重定向,则可以使用exec
命令告诉shell脚本执行期间重定向某个特定文件的描述符,如下所示:
|
|
运行结果如下所示:
|
|
exec
命令会启动一个新的shell,并将STDOUT文件描述符重定向到文件。脚本中发给STDOUT的所有输出会被重定向到文件。可以在脚本执行过程中重定向STDOUT,如下所示:
|
|
运行结果如下所示:
|
|
在这个案例中,exec命令将发给STDERR的输出重定向到了文件testerror,接下来,脚本用echo语句向STDOUT显示了几行文本,随后再次使用exec命令来将STDOUT重定向到了testout文件,虽然STDOUT被重定向了,但仍然可以将echo语句的输出发给STDERR,在此案例中,重定向到了testerror文件。
在脚本中重定向输入
在脚本中可以使用与重向STDOUT和STDERR同样的方法来将STDIN从键盘重定向到其他位置。exec命令可以将STDIN重定向到linux系统上的文件中,用法是exec 0< testfile
,这个命令会告诉shell应该从文件testfile中获得输入,而不是STDIN
,这个重定向只要在脚本需要输入时就会作用,如下所示:
|
|
结果运行如下所示:
|
|
将STDIN
重定向到文件后,当read命令试图从STDIN读入数据时,它会到文件中去读取数据,而不是键盘。
创建自己的重定向
在脚本中重定向输入和输出时,并不局限于这3个默认的文件描述符(即0,1和2)。shell中最多可以有9个打开的文件描述符。其他6个从 3 ~ 8 的文件描述符均可用作输入或输出重定向。用户可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。
创建输出文件描述符
exec 命令可以给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到你重新分配。看一个案例:
|
|
运行结果如下所示:
|
|
在这个案例中,exec命令将文件描述符3重定向到另一个文件。当脚本执行echo语句时,输出内容会像预想中那样显示在STDOUT 上。但你重定向到文件描述符3 的那行echo语句的输出却进入了另一个文件。这样你就可以在显示器上保持正常的输出,而将特定信息重定向到文件中(比如日志文件)。 也可以不用创建文件,使用exec命令来将输出追加到现有文件早,例如exec 3>> test13out
。
重定向文件描述符
看一个案例,如下所示:
|
|
运行结果如下所示:
|
|
创建输入文件描述符
可以用和重定向输出文件描述符同样的办法重定向输入文件描述符。在重定向到文件之前,先将 STDIN 文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将 STDIN 恢复到它原来的位置,如下所示:
|
|
运行结果如下所示:
|
|
在这个案例中,文件描述符6用来保存STDIN的位置,然后脚本将STDIN重定向到一个文件,read命令的所有输入都来自重定向后的STDIN(也就是输入文件),在读取了所有行之后,脚本将STDIN重定向到文件描述符6,从而将STDIN恢复到原来的位置,该脚本用了另外一个read命令来测试STDIN是否恢复正常,这将它等待键盘的输入。
创建读写文件描述符
在bash shell中,可以打开单个文件描述符来作为输入和输出,可以用一个文件描述符对同一个文件进行读写,不过使用这种方法需要谨慎,由于用户是对一个文件进行数据读写,shell会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始,如果不够小心,会生成严重的后果,如下所示:
|
|
运行结果如下所示:
|
|
在这个案例中,exec命令将文件描述符分配文件testfile以进行文件读写,接下来,通过使用分配好的文件描述符,使read命令读取文件中的第一行,然后将这一行显示在STDOUT上,最后使用echo语句将一行数据写入由同一个文件描述符打开的文件中。在运行结果中,开始运行正常,输出内容表明脚本读取了testfile文件中的第一行,如果脚本运行完毕,查看testfile文件时发现,写入文件中的数据覆盖了已有的数据。原因是,当脚本向中文件中写入数据时,它会从文件指针所处的位置开始,read命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符,在echo语句将数据输出到文件时,它会将数据放在文件指针的当前位置,覆盖了该位置的已有数据。
关闭文件描述符
如果用户创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们,有些情况下需要在脚本关闭之前手动关闭文件描述符,要关闭文件描述符,将它重定向到特殊符号&-
,在脚本中是这样的exec 3>&-
,这条语句会关闭文件描述符3,不再脚本中使用它,如下所示:
|
|
运行结果如下所示:
|
|
一旦关闭了文件描述符,就无法在脚本中写入任何数据,否则shell会生成错误消息。在关闭文件描述符时还需要注意,如果在随后的脚本中打开了同一个输出文件,shell会用一个新文件来替换已有的文件,这就意味着,如果输出数据,它就会覆盖已有鹰爪,如下所示:
|
|
结果运行如下所示:
|
|
在向test17file文件发送一个数据字符串并关闭该文件描述符之后,脚本用了 cat 命令来显示文件的内容。下一步,脚本重新打开了该输出文件并向它发送了另一个数据字符串。当显示该输出文件的内容时,你所能看到的只有第二个数据字符串。shell覆盖了原来的输出文件。
列出打开的文件描述符
bash shell中有一个lsof
命令,这个命令会列出整个Linux系统打开的所有文件描述符,lsof
命令位于/usr/sbin
目录中,这个命令会产生大量的输出,它会显示Linux系统中打开的每个文件的有产信息,这包括后台运行的所有进程,以及登录到系统的任何用户。lsof
有各种命令选项和参数可以使用,最常用的是-p
和-d
,前者用于指定进程ID(即PID),后者用于指定显示的文件描述符编号,如果要知道进程的当前PID,可以使用特殊的环境变量$$$$(shell会将它设为当前PID),-a
选项用来对其他两个选项的结果执行布尔AND
运算,如下所示:
|
|
结果显示了当前进程的国俯文件描述符(0、1和2),lsof的默认输出有7列信息,如下所示:
列 | 描述 |
---|---|
COMMAND | 正在运行的命令名的前9个字符 |
PID | 进程的PID |
USER | 进程属主的登录名 |
FD | 文件描述符号以及访问类型(r代表读,w代表写,u代表读写) |
TYPE | 文件的类型(CHR代表字符型,BLK代表块型,DIR代表目录,REG代表常规文件) |
DEVICE | 设备的设备号(主设备号和从设备号) |
SIZE | 如果有的话,表示文件的大小 |
NODE | 本地文件的节点号 |
NAME | 文件名 |
与 STDIN 、 STDOUT 和 STDERR 关联的文件类型是字符型。因为 STDIN 、 STDOUT 和 STDERR 文
件描述符都指向终端,所以输出文件的名称就是终端的设备名。现在看一个案例:
|
|
运行结果如下所示:
|
|
这个脚本创建了3个替代性文件描述符,两个是输出(3和6),一个是输入(7),在脚本运行了lsof
命令时,可以在输出中看到新的文件描述符,如果去掉输出中的第一部分,就会看到文件名的结果,文件名显示了文件描述符所使用的文件的完整路径名,它将每个文件都显示成了REG类型,这就说明它们是文件系统中的常规文件。
阻止命令输出
有些情况下,用户不想显示脚本的输出。这在将脚本作为后台进程运行时很常见,如果在运行在后台的脚本出现错误消息,shell会通过电子邮件将它们发给进程的属主。这会很麻烦,尤其是当运行会生成很多烦琐的小错误的脚本时。要解决这个问题,可以将STDERR重定向到一个叫作null文件的特殊文件。null文件跟它的名字很像,文件里什么都没有。shell输出到null文件的任何数据都不会保存,全部都被丢掉了。在Linux系统上null文件的标准位置是/dev/null。你重定向到该位置的任何数据都会被丢掉,不会显示。
|
|
也可以在输入重定向中将dev/null
作为输入文件,由于dev/null
文件不含有任何内容,用户通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建,如下所示:
|
|
文件testfile仍然存在于系统上,但现在是空文件,这是清除日志文件的一个常用的方法。
创建临时文件
Linux系统有特殊的目录,专供临时文件使用。Linux使用/tmp目录来存放不需要永久保留的文件。系统上的任何用户账户都有权限在读写/tmp目录中的文件。这个特性为用户提供了一种创建临时文件的简单方法,而且还不用操心清理工作。有个特殊命令可以用来创建临时文件。mktemp命令可以在/tmp目录中创建一个唯一的临时文件。shell会创建这个文件,但不用默认的umask值(参见第7章)。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然,root用户除外)。
创建本地临时文件
默认情况下,mktemp
会在本地目录中创建一个文件,要用mktemp
在本地目录中创建一个临时文件,只要指定一个文件名模板就行了,模板可以包含任意文本文件名,在文件名末尾加上6个x就行了,如下所示:
|
|
mktemp
命令会用6个字符码替换这6个x,从而保证文件名在目录中是唯一的。用户可以创建多个临床文件,这种用法可以使每个文件名都是唯一的。如下所示:
|
|
在脚本中使用mktemp
命令可以将文件名保存到变量中,这样就可以在后面的脚本中引用了,如下所示:
|
|
运行结果如下所示:
|
|
在这个脚本中,使用mktemp命令创建临时文件,并将文件名赋给$tempfile
变量,接着将这个临时文件作为文件描述3的输出重定向文件,在将临时文件名显示在STDOUT之后,向中临时文件中写入了几行文本,然后关闭了文件描述符,最后显示出临时文件的内容,并用rm
命令将其删除。
在/tmp
目录创建临时文件
-t
选项会强制mktemp命令在系统的临时目录来创建文件,在使用这个特性时,mktemp命令会返回用来创建临时文件的全路径,而不是仅有文件名,如下所示:
|
|
由于mktemp返回了全路径,因此用户可以在Linux系统的任意目录下引用该临时文件,不管目录在哪里,如下所示:
|
|
结果运行如下所示:
|
|
mktemp创建了临时文件时,它会返回全路径给变量,这样就可以在任何命令中使用该值来引用临时变量了。
创建临时目录
-d
选项告诉mktemp命令来创建一个临时目录,而不是临时文件,这样就能用此目录进行任何需要的操作,体积如创建其他的临时文件,如下所示:
|
|
运行结果如下所示:
|
|
这段脚本在当前目录创建了一个目录,然后它用cd命令进入该目录,并创建了两个临时文件。之后这两个临时文件被分配给文件描述符,用来存储脚本的输出。
记录消息
有时候需要将输出同时发送到显示器和日志文件,此时需要tee
命令。tee在英文就是T的意思,在Linux中,这个命令相当于管道一个T接头,它将从STDIN过来的数据同时发往两处,一处是STDOUT,另一处是tee命令所指定的文件名tee filename
,由于tee会重定向来自STDIN的数据,可以用它配合管道命令来重定向命令输出,如下所示:
|
|
输出出现在了STDOUT中,同时也写入了指定的文件中,默认情况下,tee命令会在每次使用时覆盖输出文件的内容,如下挂满:
|
|
如果需要追加数据,则要使用-a
参数,如下所示:
|
|
看一个案例:
|
|
运行结果如下所示:
|
|
从结果可以看出,用户显示输出的同时,还能保存一份输出内容。