理解Linux输入和输出
Linux中将每个对象都看作文件来处理,包括输入和输出过程,其用文件描述符来标识每个文件对象。文件描述符是一个非负整数,每个过程中一次最多可以有9个文件描述符。bash shell保留了3个文件描述符如下:
文件描述符 |
缩 写 |
描 述 |
0 |
STDIN |
标准输入 |
1 |
STDOUT |
标准输出 |
2 |
STDERR |
标准错误 |
说明:
STDIN的默认设备是键盘,STDOUT和STDERR的默认设备是显示器。重定向输出时,有3种情况:
1 仅重定向错误或输出,此时将文件描述符紧挨着放到重定向符号前边,中间不能有空格:
2 重定向错误和输出到不同的文件中,此时使用两个重定向符号:
3 重定向错误和输出到同一文件中,使用特殊的重定向符号:
对脚本中的数据进行重定向
1 临时重定向输出
想在脚本中重定向自定义的错误消息到STDERR中,如下操作: echo “This is an error message” >&2 在运行包含该行语句的脚本时,消息输出到STDERR指向的任何位置中。
2 永久重定向输出
如果脚本中包含大量需要重定向的输出信息,可以使用exec命令告诉shell在执行脚本期间将某个文件描述符重定向到固定的位置:
#! /bin/bash # redirecting output to different locations exec 2> testerror exec 1> testop echo "This section should go to the testop file" echo "but this should go to the testerror file" >&2
3 重定向输入
exec 0< inputfile
4 自定义的重定向
shell中的9个文件描述符之前介绍了3个,其余6个当作输入或者输出均可。
重定向为输出文件描述符:
exec 3> outfile echo "This should display on the monitor" echo "This should be stored in the outfile" >&3
但是如何从已重定向的文件描述符中进行恢复呢?其实很简单,只需要重定向回STDOUT中即可:
exec 3>&1 echo "This should display on the monitor" echo "This should be stored in the outfile" >&3
重定向为输入文件描述符:
exec 6<&0 exec 0<output.txt count=1 while read line do echo "Line $count: $line" count=$[ $count + 1 ] done exec 0<&6 read -p "Are you done now? " answer case $answer in Y | y) echo "Goodbye";; N | n) echo "Sorry. This is the end";; esac
重定向为输入输出文件描述符:
将一个文件描述符同时重定向为具有输入和输出功能时,由于shell维护一个内部指针,指向当前文件中的位置,任何读写操作都是从上次保存的文件指针处开始的,此时必须小心处理,否则会产生覆盖现象,如下所示:
exec 3<> output.txt read line <&3 echo "Read: $line" echo "This is a test line" >&3
说明:
read命令读取第一行数据后,文件指针移动到第二行开始,因此执行写操作时会覆盖文件output.txt中的第二行数据。
关闭文件描述符:
如果自定义了新的输入或者输出文件描述符,shell会在脚本退出时自动关闭它们。但是如果想在脚本结束之前手动关闭文件描述符,需要将其重定向到特殊符号&-,如下所示: exec 3>&-,该语句关闭文件描述符3,从而阻止在脚本中再次使用它。如果不小心再次使用了该文件描述符,那么会得到bad file descriptor错误提示。
列出打开的文件描述符
9个文件描述符中,很难记住哪个文件描述符被重定向到哪,bash shell提供了lsof命令。lsof命令列出了整个Linux系统中打开的所有文件描述符。由于该命令向非系统管理员提供了Linux相关的信息,因此,很多Linux系统隐藏了该功能,在centos中,通过which lsof,可以得到其被存放在/usr/sbin/lsof中,以全路径运行该命令,会产生大量的输出。可以使用-p指定具体的PID,也可以使用-d来指定具体的文件描述符,-a选项对任何两个选项执行布尔AND运算。要想知道当前进程的PID,可以使用环境变量"$$"。
lsof命令结果描述:
COMMAND |
正在运行的命令名的前9个字符 |
PID |
进程ID |
USER |
进程属主的登录名 |
FD |
文件描述符[r表示可读,w表示可写,u表示可读写] |
TYPE |
文件类型[CHR表示字符型,BLK表示块型,DIR表示目录,REG表示常规文件] |
DEVICE |
设备号[主设备号和从设备号] |
SIZE |
文件大小 |
NODE |
本地文件节点数 |
NAME |
文件名 |
我们知道0,1,2分别表示STDIN,STDOUT,STDERR,从上边运行结果可知这三个文件描述符关联的文件类型都是字符型,都指向终端,所以输出文件名都是终端的设备名。这三种标准文件都支持读和写,虽然我们一般不用STDIN来写数据,也不用STDOUT来读数据,这样看起来就太奇怪了。
阻止命令输出
可以将STDERR重定义到一个称为null的特殊文件,Linux中,null文件的位置是/dev/null,任何重定向到该位置的文件都会被丢弃而不进行显示。当然,也可以将/dev/null作为输入文件,这样由于/dev/null文件中不包含任何内容,其作用是可以快速删除现有文件中的数据而不用删除文件本身:
这是清除日志文件的一个通用方法,既可以清除文件内容,又可以保留文件存在。
创建临时文件
Linux使用/tmp目录来存放临时文件,多数Linux系统在启动时会自动删除/tmp目录中的文件。系统中的任何用户都有权限对/tmp目录进行读和写。
mktemp命令用于在当前目录中创建临时文件。默认情况下,使用mktemp命令时只需要指定一个文件名模板就可以了。模板可以包含任意文本,在文件名末尾加上6个X就可以了,如下:
mktemp命令使用6个字符代替6个X,从而确保文件名在本地目录中的唯一性。
如果在使用mktemp命令时加上-t选项,那么该命令会在/tmp目录下创建临时文件,并返回临时文件的全路径名,如下所示:
mktemp加上-d选项表示在当前目录下创建临时目录。
记录消息
如果需要想输出发送到显示器的同时又记录到日志文件中,一种方式是使用两次重定向,另一种方式时使用tee命令。tee命令相当于管道的一个T型接口,其将STDIN接受过来的数据同时发送给两个目的地:一个是STDOUT,另一个是tee指定的文件名:tee filename
如果使用tee命令时加上了-a选项,表示在testfile文件中追加数据而不是覆盖,如下所示: