[Linux Shell学习系列十一]脚本输入处理-3.文件描述符

D22

Shell有时会使用文件描述符(fd)的文件。一般使用范围是数字0~9.重定向时大于9的文件描述符要谨慎使用,因为可能与Shell内部使用的文件描述符冲突。

文件描述符可以包含多个数字为,如文件描述符001与01与1是相同的。多种操作(如exec命令)都可以将文件描述符与特定的文件联系起来。

有些文件描述符是在Shell启动时被建立的,这就是我们前面介绍的标准输入、标准输出和标准错误(0、1、2)文件描述符。

 

1. 使用exec命令

Bash的内部命令exec的功能之一是允许我们操作文件描述符。如果在exec之后没有指定命令,则exec命令之后的重定向将更高当前Shell的文件描述符。‘’

如:执行"exec 2> file"后运行的所有命令,都会将其错误信息发送到file中。
示例:应用exec指定文件描述符

$ cat readFileAndInput.sh
#!/bin/bash
#202006

if [ $# -lt 1 ]
then
        echo "Usage: `basename $0` FILEPATH"
        exit
fi

file=$1

while read -r line #从输入中读取一行
do
        echo $line

        read -p "Press any key to continue..." -n 1

done < $file #指定file代表的文件为while循环的标准输入

#此时没有执行read命令,因为将指定的文件作为while循环的标准输入,
#而read也继承了这个文件描述符(即标准输入),
#因此read从重定向后的标准输入读取,而不是从默认的标准输入设备(键盘)读取
$ ./readFileAndInput.sh list.txt 
ab dde
b dde

bde
33

$ cat  readFileAndInput_1.sh
#!/bin/bash
#202006

if [ $# -lt 1 ]
then
        echo "Usage: `basename $0` FILEPATH"
        exit
fi

exec 3< $1 #将脚本的第一个参数作为输入文件,并指定其文件描述符为3

while read -u 3 line #read命令的-u选项表示从指定的文件描述符中读取内容,而不是从标准输入读取
do
        echo $line

        read -p "Press any key to continue..." -n 1

done 

exec 3<&- #关闭文件描述符3

$ ./readFileAndInput_1.sh list.txt #执行read命令,并从键盘读取,每次输入空格
ab dde
Press any key to continue... ab dde
Press any key to continue... 
Press any key to continue... 
Press any key to continue... abde
Press any key to continue... 333
Press any key to continue... a
Press any key to continue... 

 

2. 指定用于输入的文件描述符

文件描述符0、1、2分别为标准输入、标准输出和标准错误。

Shell允许你给一个输入文件或输出文件指定一个文件描述符,可以提高文件读取和写入的性能,被称为用户自定义文件描述符。

语法:exec [n] < file

n:为文件描述符,不指定则为标准输入(文件描述符0);

上述的输入重定向会在文件描述符n上打开一个用于读取的文件file。

$ grep a list.txt #不使用文件描述符
ab dde
ab   dde
abde
a

$ exec 3< list.txt #指定文件描述符并打开文件用于读取数据

$ grep a <& 3 
#这里使用的<&,也是一种重定向操作符,用于复制输入文件描述符
#grep命令将文件描述符3复制到了标准输入,而list.txt又是在文件描述符3上打开以用于被命令读取,因此grep命令读取的是文件描述符3的内容
ab dde ab dde abde a $ exec
3<&- #关闭文件描述符 $ grep a <& 3 #此时文件描述符无效 -bash: 3: Bad file descriptor

 

操作符<&,用于复制输入文件描述符。

语法: [n]<&word

如果word是数字,则用n表示的文件描述符被作为文件描述符word的副本。如果word指定的文件描述符没有打开以用于读取,则会发生重定向错误。

如果没有指定n,默认为标准输入。

 

D23

$ cat readfd.sh 
#!/bin/bash
#202006

if [ $# -ne 1 ] || [ ! -f $1 ]
then
        echo "Usage: `basename $0` <filepath>"
        exit 1
fi

exec 3< $1 #将第一个参数作为输入文件并指定文件描述符3

cat <& 3 #将标准输入作为文件描述符3的副本

exec 3<&- #关闭文件描述符3


$ ./readfd.sh list.txt 
ab dde
ab   dde


abde
333
a

 

3. 指定用于输出的文件描述符

给一个输出文件指定文件描述符。

语法:exec [n]> file

[n]为文件描述符,如果不指定n,则表示标准输出(即文件描述符1)。

上述的输出重定向会在文件描述符n上打开一个用于写入的文件file。如果文件file不存在,则会被创建。如果文件已存在,则它被清空为0字节。

$ exec 4> output.txt

$ cat output.txt #没有内容

$ date >&4 #将命令的执行结果写入文件描述符4打开的文件
#这里的操作符>&不是表示标准输出和标准错误同时重定向的操作符,而是用于复制输出文件操作符。
$
cat output.txt Fri Jun 5 10:46:02 CST 2020

操作符>&
语法:[n]>&word
如果n没有指定,则默认使用标准输出。如果数字word指定的文件描述符没有打开用于输出,则会发生重定向错误。

$ cat execstdout.sh 
#!/bin/bash
#202006

LOGFILE=logfile.txt

exec 6>&1 #重定向文件描述符6到标准输出,这里实际是保存当前的标准输出的值,后面恢复的时候会用到

exec > $LOGFILE #重定向标准输出到变量LOGFILE所代表的文件logfile.txt


#以下代码都被发送到标准输出,此时被重定向到logfile.txt
echo -n "Logfile: "
date
echo "-----------------------------"
echo 

echo "Output of \"uname -a\" command"
echo
uname -a
echo;echo
echo "Output of \" df\" command"
echo 
df

#恢复标准输出(通过之前保存的文件描述符6),并关闭文件描述符6
exec 1>&6 6>&-

#以下输出到标准输出,即屏幕
echo 
echo "== stdout now restored to default =="
echo 
uname -a
echo 

exit 0


$ ./execstdout.sh 

== stdout now restored to default ==

Linux host-192-168-164-20.novalocal 3.10.0-1062.el7.x86_64 #1 SMP Thu Jul 18 20:25:13 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

$ cat logfile.txt 
Logfile: Fri Jun  5 10:55:10 CST 2020
-----------------------------

Output of "uname -a" command

Linux host-192-168-164-20.novalocal 3.10.0-1062.el7.x86_64 #1 SMP Thu Jul 18 20:25:13 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux


Output of " df" command

Filesystem     1K-blocks     Used Available Use% Mounted on
devtmpfs        16379660        0  16379660   0% /dev
tmpfs           16390148        0  16390148   0% /dev/shm
tmpfs           16390148    49836  16340312   1% /run
tmpfs           16390148        0  16390148   0% /sys/fs/cgroup
/dev/vda1      103079844 33653904  65173492  35% /
tmpfs            3278032        0   3278032   0% /run/user/1001

 

4. 关闭文件描述符

语法:

[n]<&- #关闭标准输入

[n]>&- #关闭标准输出

如:关闭标准输入<&-,关闭标准错误就是2>&-

尽管操作系统会清理无用垃圾,但适时地关闭你自己打开的文件描述符仍然是一个好习惯。

$ exec 5>file
...#使用文件描述符5的命令

$ exec 5>&- 
#后面不再需要文件描述符5

尽管使用command 2>&-将标准错误丢弃掉,但不确定所有应用程序在关闭标准错误的情况下都能正常运行。因此建议使用2>/dev/null来丢弃标准错误输出。

 

5. 打开用于读和写的文件描述符

Bash支持使用如下语法在文件描述符上打开一个既可读又可写的文件:

语法:exec [n]<>file

[n]为文件描述符,如果不指定n,默认表示标准输入。如果file不存在,则会被创建。符号<>是Bash中的菱形操作符,用于打开一个可读写的文件。

$ echo "one two" > logfile.txt #在logfile.txt中写入"one two"

$ exec 4<> logfile.txt #在文件操作符4上打开用于读写的文件logfile.txt

$ read -n 3 var <& 4 #从文件操作符4中读取3个字符
$ echo $var
one

$ echo -n + >& 4 #写入内容

$ exec 4>&- #关闭文件操作符4

$ cat logfile.txt 
one+two
#这里你可能以为是one two+,但因为操作符<>会使后续的读写操作跟随先前读写操作的位置,所以+会被写到之前read -n 3的后面,即第4个字符的位置

 

6. 在同一脚本中使用exec进行输入和输出重定向

本节通过实例学习如何在同一脚本中使用exec命令既进行输入重定向又进行输出重定向。

$ cat readwritefd.sh 
#!/bin/bash
#202006

exec 3< resolv.conf #在文件描述符3上打开用于读取的文件(输入)
exec 4> output.txt #在文件描述符4上打开用于写入的文件(输出)

echo "My pid is: $$"
mypid=$$

echo "Currently open files by `basename $0` scripts: "
ls -l /proc/$mypid/fd #打印当前脚本打开的文件描述符和相关联的文件


read -u 3 a b #从文件描述符3上读取文件resolv.conf第一行的内容,并分别赋值给变量a和b

echo "Data read from fd # 3:"
echo $a $b

echo "Writing data read from fd 3 to fd 4..."
echo "Field #1 - $a " >&4 #在文件描述符4上向文件output.txt写入数据
echo "Field #2 - $b " >&4

exec 3<&- #关闭文件描述符3
exec 4<&- #关闭文件描述符4

echo "Currently open files by `basename $0` scripts: "
ls -l /proc/$mypid/fd #关闭文件描述符3和4后,再次打印当前脚本打开的文件描述符和相关联的文件

执行:

$ cat resolv.conf 
nameserver 192.168.0.1

$ ./readwritefd.sh 
My pid is: 6488
Currently open files by readwritefd.sh scripts: 
total 0
lrwx------. 1 user1 user1 64 Jun  5 15:54 0 -> /dev/pts/2
lrwx------. 1 user1 user1 64 Jun  5 15:54 1 -> /dev/pts/2
lrwx------. 1 user1 user1 64 Jun  5 15:54 2 -> /dev/pts/2
lr-x------. 1 user1 user1 64 Jun  5 15:54 255 -> /home/user1/test/readwritefd.sh
lr-x------. 1 user1 user1 64 Jun  5 15:54 3 -> /home/user1/test/resolv.conf
l-wx------. 1 user1 user1 64 Jun  5 15:54 4 -> /home/user1/test/output.txt
Data read from fd # 3:
nameserver 192.168.0.1
Writing data read from fd 3 to fd 4...
Currently open files by readwritefd.sh scripts: 
total 0
lrwx------. 1 user1 user1 64 Jun  5 15:54 0 -> /dev/pts/2
lrwx------. 1 user1 user1 64 Jun  5 15:54 1 -> /dev/pts/2
lrwx------. 1 user1 user1 64 Jun  5 15:54 2 -> /dev/pts/2
lr-x------. 1 user1 user1 64 Jun  5 15:54 255 -> /home/user1/test/readwritefd.sh

$ cat output.txt 
Field #1 - nameserver 
Field #2 - 192.168.0.1

注:

proc文件系统(/proc)是一个被作为内核数据结构接口的伪文件系统。

Linux系统中的每一个运行的进程在/proc下都有一个对应的以数字命名的子目录,这个数字就是进程的ID号(PID)。每个子目录都包含有伪文件和目录。

而/proc/[PID]/fd就是这些伪目录之一,其中的每一个条目对应一个该进程打开的文件,这些条目用文件描述符命名,并软链接到实际的文件(也有网络接口)。就是上面的3指向resolv.conf,4指向output.txt。

如执行一个top命令,查看其fd:

$ ps -ef|grep top #查看进程信息
user1    6522  2446  0 16:01 pts/2    00:00:00 top

$ ls -l /proc/6522/fd #PID为6522
total 0
lrwx------. 1 user1 user1 64 Jun  5 16:01 0 -> /dev/pts/2
lrwx------. 1 user1 user1 64 Jun  5 16:01 1 -> /dev/pts/2
l-wx------. 1 user1 user1 64 Jun  5 16:01 2 -> /dev/null
lrwx------. 1 user1 user1 64 Jun  5 16:01 3 -> /dev/pts/2
lr-x------. 1 user1 user1 64 Jun  5 16:01 4 -> /proc/stat
lr-x------. 1 user1 user1 64 Jun  5 16:01 5 -> /proc/uptime
lr-x------. 1 user1 user1 64 Jun  5 16:01 6 -> /proc/meminfo
lr-x------. 1 user1 user1 64 Jun  5 16:01 7 -> /proc/loadavg

 

本节结束

 

posted @ 2020-06-03 11:06  workingdiary  阅读(1257)  评论(0)    收藏  举报