十、文件描述符

标准文件描述符

  1. 文件描述符是非负整数,可以唯一标识会话中打开的文件
  2. 每个进程一次最多能有9个文件描述符
  3. 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

 

创建临时文件

  1. 临时文件存放目录/tmp
  2. linux启动时会自动删除/tmp目录下所有文件
  3. 任何用户都有权限读写/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章

posted @ 2020-08-19 11:15  努力吧阿团  阅读(478)  评论(0编辑  收藏  举报