十、文件描述符
标准文件描述符
- 文件描述符是非负整数,可以唯一标识会话中打开的文件
- 每个进程一次最多能有9个文件描述符
- bash shell保留前三个文件描述符(0、1、2)
文件描述符 | 缩写 | 描述 |
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
STDIN
标准输入描述符,终端默认标准输入是键盘
可以使用重定向<指定文件替换标准输入文件描述符
STDOUT
标准输出描述符,终端默认是输出到显示器
可以使用重定向>输出到指定文件
STDERR
标准错误输出描述符,默认输出到显示器
不会随着STDOUT的重定向而改变,还是会输出到显示器
[root@tzPC 15Unit]# ls -al badfile > test3 ls: 无法访问badfile: 没有那个文件或目录
重定向错误
STDERR文件描述符为2,需要放在重定向符号前
[root@tzPC 15Unit]# ls -al test batest test3 2>test4 -rw-r--r--. 1 root root 0 8月 19 10:13 test3 [root@tzPC 15Unit]# cat test4 ls: 无法访问test: 没有那个文件或目录 ls: 无法访问batest: 没有那个文件或目录
重定向错误和数据
[root@tzPC 15Unit]# ls -al test test2 test3 2>test6 1>test7 [root@tzPC 15Unit]# cat test6 ls: 无法访问test: 没有那个文件或目录 ls: 无法访问test2: 没有那个文件或目录 [root@tzPC 15Unit]# cat test7 -rw-r--r--. 1 root root 0 8月 19 10:13 test3
重定向错误和数据到同一个文件
这种方式看起来没啥用,但是会优先显示错误信息
[root@tzPC 15Unit]# ls -al test test3 test2 &> test7 [root@tzPC 15Unit]# cat test7 ls: 无法访问test: 没有那个文件或目录 ls: 无法访问test2: 没有那个文件或目录 -rw-r--r--. 1 root root 0 8月 19 10:13 test3
可以看到test跟test2的错误信息会优先显示到开头
脚本中重定向输出
- 临时重定向
- 永久重定向
临时重定向
效果
[root@tzPC 15Unit]# bash test8.sh This is an error This is normal output [root@tzPC 15Unit]# bash test8.sh 2>test9 This is normal output [root@tzPC 15Unit]# cat test9 This is an error
脚本
[root@tzPC 15Unit]# cat test8.sh #!/bin/bash #testing STDERR messages echo "This is an error" >&2 #将这条错误信息重定向到文件描述符 echo "This is normal output"
可以看到脚本中先将错误信息重定向到错误文件描述符,再通过错误文件描述符重定向到文件test9
错误信息是临时重定向到文件中的,脚本还是会输出错误信息。
永久重定向
使用exec命令会启动一个新的shell将STDOUT文件描述符重定向到文件
脚本
[root@tzPC 15Unit]# cat test10.sh #!/bin/bash #redirecting all output to a file exec 2>testerror echo "This is the start of the script" exec 1>testout echo "This is output should go to the testout file" echo "This is an error" >&2
效果
[root@tzPC 15Unit]# bash test10.sh This is the start of the script [root@tzPC 15Unit]# cat testout This is output should go to the testout file [root@tzPC 15Unit]# cat testerror This is an error
可以看到exec 2>testerror会把重定向到错误描述符的语句重定向到文件,其他的会原样输出,exec 1>testout会将后面的语句都重定向输出到文件,除了错误重定向。
错误信息永久的重定向到了文件,不会在脚本中输出。
脚本中重定向输入
使用exec命令
格式
exec 0< testfile
脚本
[root@tzPC 15Unit]# bash test12.sh Line #1: this is the first line. Line #1: this is the second line. [root@tzPC 15Unit]# cat test12.sh #!/bin/bash #redirecting file input exec 0<testfile count=1 while read line do echo "Line #$count: $line" count=$[ $conug + 1 ] done
创建自己的重定向
shell中最多有9个文件描述符,3~8文件描述符均可用作输入或输出重定向。
创建输出文件描述符
使用exec命令来分配输出文件描述符,一旦分配一直有效,直到重新分配或关闭该文件描述符。
效果
[root@tzPC 15Unit]# bash test13.sh This should display on the monitor The this should be back on the monitor [root@tzPC 15Unit]# cat test13out and this should be stored in the file
脚本
[root@tzPC 15Unit]# cat test13.sh #!/bin/bash #using an alternative file descriptor exec 3>test13out #创建一个输出描述符,输出到文件test13out echo "This should display on the monitor" echo "and this should be stored in the file" >&3 echo "The this should be back on the monitor"
或者追加到文件
exec 3>>test13out
重定向文件描述符
将原来输出的位置重定向到一个文件描述符,在用该文件描述符重定向回输出位置
[root@tzPC 15Unit]# cat test14.sh #!/bin/bash #storing STDOUT, then coming back to it exec 3>&1 #发送给文件描述符3的输出都将出现在显示器上 exec 1>test14out #发送到文件描述符1的输出都将重定向到文件test14out echo "This should store in the output file" echo "along with this line" #以上两句是发送到显示器上的,最后会重定向到test14out文件 exec 1>&3 #发送到文件描述符1也就是到test14out文件的输出会重定向到文件描述符3,而3是重定向到显示器的,所以最后会在显示器上输出 echo "Now things should be back to normal" [root@tzPC 15Unit]# bash test14.sh Now things should be back to normal
创建输入文件描述符
[root@tzPC 15Unit]# cat test15.sh #!/bin/bash #redirecting input file descriptors exec 6<&0 #文件描述符0默认从键盘输入,保存给文件描述符6 exec 0<testfile count=1 while read line do echo "Line #$count: $line" count=$[ $count + 1 ] done exec 0<&6 #此时文件描述符0定义为从6输入,6是从键盘输入,故下面代码生效 read -p "Are you done now? " anser case $anser in Y|y) echo "Goodbye";; N|n) echo "Sorry,this is the end.";; esac [root@tzPC 15Unit]# bash test15.sh Line #1: this is the first line. Line #2: this is the second line. Are you done now? y Goodbye
创建读写文件描述符
原来testfile文件内容
[root@tzPC 15Unit]# cat testfile this is the first line. this is the second line. this is the third line.
运行脚本
[root@tzPC 15Unit]# cat test16.sh #!/bin/bash #testing input/ouput file descriptor exec 3<>testfile #创建文件描述符3用于读写testfile文件 read line <&3 echo "Read: $line" #读取testfile文件第一行 echo "This is a test line" >&3 #此时将This is a test line写入第二行数据
这里书里提到了一个内部指针,大致意思是当脚本向文件中写入数据时,read命令读了文件第一行数据,指针会指向第二行,这时第二行数据被写入,指针指向第三行,此时read命令读取第三行数据,内部指针就是一个读写顺序指向过程。
此时testfile文件内容为
[root@tzPC 15Unit]# cat testfile this is the first line. This is a test line ine. this is the third line.
这里怎么跟我想的不一样??ine.是什么鬼?
关闭文件描述符
创建的文件描述符,shell会在脚本退出时自动关闭。
手动关闭文件描述符3的语法如下
exec 3>&-
例子
badtest.sh:行6: 3: 错误的文件描述符 [root@tzPC 15Unit]# cat badtest.sh #!/bin/bash #testing closing file descriptors exec 3>test17file echo "This is a test line of data">&3 exec 3>&- echo "This won't work" >&3
可以看到,关闭了文件描述符3,脚本在向他写入数据时会报错。
列出打开的文件描述符
lsof命令会列出整个Linux系统打开的所有文件描述符
他会向非系统管理员用户提供linux系统信息
位于/usr/sbin目录
默认显示当前Linux系统上打开的每个文件的有关信息,包括后台运行的所有进程以及登录到系统的所有用户信息。
最常用的参数
-p:指定进程ID
-d:指定要显示的文件描述符编号
-a:筛选同时满足-p跟-d两个选项的结果
特殊环境变量$$会显示当前进程ID
[root@tzPC ~]# lsof -a -p $$ -d 0,1,2 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 42755 root 0u CHR 136,2 0t0 5 /dev/pts/2 bash 42755 root 1u CHR 136,2 0t0 5 /dev/pts/2 bash 42755 root 2u CHR 136,2 0t0 5 /dev/pts/2
各列的含义
COMMAND 正在运行的命令名的前9个字符
PID 进程的PID
USER 进程属主的登录名
FD 文件描述符号以及访问类型(r代表读,w代表写,u代表读写)
TYPE 文件类型(CHR代表字符类型,BLK代表块型,DIR代表目录,REG代表常规文件)
DEVICE 设备的设备号(主设备号和从设备号)
SIZE 文件大小
NODE 本地文件节点号
NAME 文件名
与STDIN、STDOUT和STDERR关联的文件类型为字符型,因为他们描述符都指向终端,所以文件名就是终端的设备名。
[root@tzPC 15Unit]# cat test18.sh #!/bin/bash #testing lsof with file descriptors exec 3>test18file1 exec 6>test18file2 exec 7<testfile /usr/sbin/lsof -a -p $$ -d0,1,2,3,6,7 [root@tzPC 15Unit]# bash test18.sh COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 75272 root 0u CHR 136,2 0t0 5 /dev/pts/2 bash 75272 root 1u CHR 136,2 0t0 5 /dev/pts/2 bash 75272 root 2u CHR 136,2 0t0 5 /dev/pts/2 bash 75272 root 3w REG 253,0 0 35469258 /root/script/15Unit/test18file1 bash 75272 root 6w REG 253,0 0 35469259 /root/script/15Unit/test18file2 bash 75272 root 7r REG 253,0 73 35469233 /root/script/15Unit/testfile
阻止命令输出
黑洞文件/dev/null
可以用来快速清除现有文件的数据,而不用删除在创建。
或用来快速清除日志文件。
[root@tzPC 15Unit]# ls -al badfile test16 2> /dev/null
创建临时文件
- 临时文件存放目录/tmp
- linux启动时会自动删除/tmp目录下所有文件
- 任何用户都有权限读写/tmp目录中的文件
创建本地临时文件
mktemp命令可以在/tmp目录中创建一个唯一的临时文件
文件权限为属主,除了root跟属主谁也不能访问
使用mktemp命令创建临时文件需要在文件名末尾加上6个X
[root@tzPC 15Unit]# mktemp testing.XXXXXX testing.SNK6dI [root@tzPC 15Unit]# mktemp testing.XXXXXX testing.O8M7X3 [root@tzPC 15Unit]# mktemp testing.XXXXXX testing.sLT37i [root@tzPC 15Unit]# mktemp testing.XXXXXX testing.KPs2vn [root@tzPC 15Unit]# ll testing* -rw-------. 1 root root 0 8月 19 23:40 testing.KPs2vn -rw-------. 1 root root 0 8月 19 23:40 testing.O8M7X3 -rw-------. 1 root root 0 8月 19 23:40 testing.sLT37i -rw-------. 1 root root 0 8月 19 23:39 testing.SNK6dI
mktemp命令会用6个字节码替换6个X以此保证文件名唯一存在
在脚本中使用需把文件名保存到变量中方便引用
[root@tzPC 15Unit]# cat test19.sh #!/bin/bash #creating and using a temp file tempfile=$(mktemp test19.XXXXXX) exec 3>$tempfile echo "This script writes to temp file $tempfile" echo "This is the first line" >&3 echo "This is the second line" >&3 echo "This is the last line" >&3 exec 3>&- echo "Done creating temp file.The contents are:" cat $tempfile rm -rf $tempfile 2>/dev/null
在/tmp目录创建临时文件
mktemp -t参数会在系统临时目录创建临时文件,同时返回创建临时文件的全路径
[root@tzPC 15Unit]# mktemp -t test.XXXXXX /tmp/test.XFSQHD
因为返回的全路径,可以在linux系统任何目录下引用该临时文件。
创建临时目录
mktemp -d参数用来创建临时目录。
[root@tzPC 15Unit]# cat test21.sh #!/bin/bash #using a temporary directory tempdir=$(mktemp -d dir.XXXXXX) echo "创建的临时目录为:`pwd`$tempdir" cd $tempdir tempfile1=$(mktemp temp.XXXXXX) tempfile2=$(mktemp temp.XXXXXX) exec 7> $tempfile1 exec 8> $tempfile2 echo "Sending data to directory $tempdir" echo "This is a test line of data for $tempfile1">&7 echo "This is a test line of data for $tempfile2">&8
记录消息
tee命令
将输出同时发送到显示器跟文件
[root@tzPC 15Unit]# date | tee testfile 2020年 08月 20日 星期四 00:13:31 CST [root@tzPC 15Unit]# cat testfile 2020年 08月 20日 星期四 00:13:31 CST
tee -a将数据追加到文件
[root@tzPC 15Unit]# who | tee -a testfile root tty1 2020-08-16 22:20 [root@tzPC 15Unit]# cat testfile 2020年 08月 20日 星期四 00:13:31 CST root tty1 2020-08-16 22:20
脚本实战
[root@tzPC 15Unit]# cat test23.sh #!/bin/bash #read file and create INSERT statements for MySQL outfile='members.sql' IFS=',' while read lname fname address city state zip do cat >> $outfile <<EOF INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('$lname','$fname'.'$address','$city','$state','$zip'); EOF #cat>>将cat命令的输出追加重定向到$outfile文件,cat命令的输入为后面的数据EOF标记了数据的起始跟结尾 done < ${1} #${1}代表第一个命令行参数,指明要读取的文件
members.csv文件内容如下
[root@tzPC 15Unit]# cat members.csv Blum,Richard,123 Main St.,Chicago,IL,60601
运行脚本,这里书中写成./test23 < members.csv 有误,应该如下写法
[root@tzPC 15Unit]# bash test23.sh members.csv
查看members.sql文件
[root@tzPC 15Unit]# cat members.sql INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('Blum','Richard'.'123 Main St.','Chicago','IL','60601');
学习来自:《Linux命令行与Shell脚本大全 第3版》第15章