[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
本节结束

浙公网安备 33010602011771号