高级重定向的示例(shell 进阶)

阅读: 评论:0

高级重定向的示例(shell 进阶)

高级重定向的示例(shell 进阶)

目录

1、简单示例

2、文件描述符的备份与还原

3、通过高级重定向实现真正的临时文件

4、多进程控制

5、exec创建输出/输入文件描述符

输出重定向

 输入重定向

6、exec创建读写文件描述符

7、关闭exec创建的文件描述符

8、如何确定fd 已被使用 



1、简单示例

echo 1234567890 > File
exec 3<> File
read -n 4 <&3
echo -n . >&3
exec 3>&-
cat File

在shell中最多可以有9个打开的文件描述符。0 1 2是每个程序都会默认打开的3个fd。其他6个从3~8的文件描述符均可用作输入或输出重定向。可以自由定义。 

执行如上shell 会得到什么结果呢?

分析一下

1、将字符串覆盖重定向输出到File中

2、<> 代表可读可写模式,以可读可写的方式打开fd3

3、-n 代表以字节方式读取,从fd=3读取四个字节

-n nchars
                     read returns after reading nchars characters rather than waiting for a complete line of input, but honors a delimiter if fewer than nchars
                     characters are read before the delimiter.

4、不换行将点覆盖冲定向到fd3

5、&- 代表关闭描述符  ,关闭fd3

6、查看File

其中重点在于第三四行:

read和echo共享同一个偏移量指针。read读取四个字节后指针放在数字串5的位置. 接着echo将. 写在了5那个位置。

所以结果是1234.67890 

此外read读取的变量一般存在$REPLY变量中。

2、文件描述符的备份与还原

先理解这两个概念:

[n]<&word:将文件描述符n复制于word 代表的文件或描述符。可以理解为文件描述符n重用word代表的文件或描述符,即word原来对应哪个文件,现在n作为它的副本也对应这个文件。n不指定则默认为0(标准输入就是0),表示标准输入也将输入到word所代表的文件或描述符中。
[n]>&word:将文件描述符n复制于word 代表的文件或描述符。可以理解为文件描述符n重用word代表的文件或描述符,即word原来对应哪个文件,现在n作为它的副本也对应这个文件。n不指定则默认为1(标准输出就是1),表示标准输出也将输出到word所代表的文件或描述符中。

​​​​​​​

exec 6>&1
exec > /
echo "---------------"
exec 1>&6 6>&-
echo "==============="

1、exec打开fd6,fd6 指向fd1,fd1就是当前的终端。所以这时候fd1和fd6都指向了当前终端。

2、exec后面省略1. 此时fd1 执行/。 fd6还是指向当前终端

3、在当前终端输出字符串 ???错。这时候当前终端不会显示,因为fd1指向的是,所以终端不会显示任何字符串。

4、将fd1 和fd6 合并指向fd6。那就代表之前fd1 指向 的指向断开了。紧接着将fd6关闭,fd1又指向了当前终端。

5、当前终端(fd1)输出字符串

其中第一步属于fd的备份。第四步属于fd的还原。

 再看个例子加深下印象

直接将fd1 还原到终端屏幕上来

exec打开fd1 并指向

如何将fd1还原到当前终端呢?

/proc/self/fd/{0,1,2} -> /dev/pts/N(N是终端号)

不管是标准输入、输出、错误都会指向一个终端,想要fd1重回终端只需要exec 指向当前终端。

输入w在查看。

exec >/dev/pts/2就可以回到当前终端了。

需要注意:exec开启的fd,类似于一个指针,往其中写一些数据,指针向前移动。即便是覆盖式重定向,也不会出现覆盖的问题。

3、通过高级重定向实现真正的临时文件

平时我们所谓的临时文件,大多数都是先创建然后删除文件,其实这并不是真正的临时文件,只是自己定义的一个概念,那真正的临时文件是什么呢?

创建之后立即删除,维持其fd打开,基于其fd做事情才是真正的临时文件。

#!/bin/bash
# open fd=3 and remove file
exec 3<> /tmp/${0}${$}.temp
rm -rf /tmp/${0}${$}.temp
# file deleted
ls /proc/self/fd
lsof -n | grep -E 'temp.*delete[d]'
# write to fd=3
echo "hello world" >&3
# read from fd
#cat <&3
cat /proc/self/fd/3
# close fd
exec 3<&-
lsof -n | grep -E 'temp.*delete[d]'

脚本执行的结果

 脚本以及结果的解释: 

exec 3<> /tmp/${0}${$}.temp
exec 开启fd3,以可读可写模式执行指定的文件。

  • ${0} 代表的当前shell的文件名
  • ${$} 代表当前的PID

紧接着删除文件,尽管该文件已经被删除,但是可以通过fd3继续写入,此时写入的数据是存储在内存中的。

ls /proc/self/fd 查看当前进程打开的fd列表。

这个fd是0 1 2 3 4. 为何会是四个呢?

ls 进程是fork自该脚本文件,改脚本文件已经定义了fd3,ls 进程继承了fd3 ,自己打开的fd4。所以共4个fd。

lsof -n查看temp文件确实已经被删除了。此时这种状态代表该fd还在内存中存在,这也是经常用于文件恢复的一个手法。

echo "hello world" >&3 向fd3 写入内容,此时的内容是保存在内存中而不是硬盘中。

如何读取上面写入的内容呢?

cat < &3 是读取不到内容的。该方式读取是从指针指向hello word之后的位置读取,因此读取不到内容。

使用cat /proc/self/fd/3 直接从fd3中读取内容。这代表重新打开fd,指针是从文件的头部开始的。

exec 3<&- 关闭fd3

lsof -n此时已经查看不到改文件了,该文件彻底从内存中消失了。

4、多进程控制

 xargs 多进程示例
 

redirect]# time bash -c 'echo -e "1n2n3n4" | xargs -i -n 1 -P 4 sleep {}' real	0m4.006s
user	0m0.002s
sys	0m0.004s

分别将1 2 3 4传递给sleep最多4个进程来处理,所以共花费了4s。

bash -c代表执行后面的shell

echo -e 代表启用转义字符。

xargs -i 是用于传递到多个位置时使用

xargs -n 1代表一个分一段

-P 代表最多4个进程跑

去掉多进程参数,则所需时间为1 + 2 + 3 + 4 = 10s

redirect]# time bash -c 'echo -e "1n2n3n4" | xargs -i -n 1 sleep {}' real	0m10.007s
user	0m0.005s
sys	0m0.002s

看一个多进程的例子:

#!/bin/bash
# 脚本中有后台,捕获它们
trap 'kill 0;exit 1' SIGINT SIGTERM
# 多少个进程数,默认5
proc_count=5
# 创建临时的命名管道,并打开它
tempfifo=/tmp/temp_${0}_${$}.fifo
mkfifo $tempfifo
exec 5<> $tempfifo
rm -rf $tempfifo
# 向命名管道中写入指定进程数量的空行,以空行数量描述进程池,一个空行代表一个进程
for i in $(seq 1 $proc_count);doecho
done >&5
# 多进程工作点
while true;do# 从命名管道中读一个空行read -u5date +"%T"{ sleep 3s; echo >&5; } & #每次执行循环体内的内容进程少一个,需要使用echo >&5向进程池注入一个进程
done;wait #父进程中等待子进程执行完

解释:

mkfifo $tempfifo ;创建于一个命名管道
exec 5<> $tempfifo ; exec打 可读可写打开并分配fd5。 此处是如何知道fd5 是空闲的呢? 可能就是先分配,如果失败就报错了。

 运行结果

]# sh -x test2.sh 

pgrep -f 'sleep' 查看一直是有五个进程去执行。 

也可以使用pstree -p | grep 'sleep' 查看

借鉴这个思路可以实现,多进程ping. 

# 从命名管道中读一个空行read -u5date +"%T"{ ping -c1 -w1 192.168.56.10-254; echo >&5; } & 

5、exec创建输出/输入文件描述符

输出重定向

#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1
exec 1>test14out
echo "This should store in the output file"
echo "along with this line."
echo "hehehehehehhe." >&1
echo "hhaaha" >&3
exec 1>&3
echo "Now things should be back to normal"
echo "hhe" >&1
echo "aaahhe" >&3

 输入重定向

#!/bin/bash
exec 6<&0 # 开启fd 6,并指向STDIN。此时从fd6 中读取数据相当于从STDIN中读取exec 0< testfile # 让STDIN从testfile读取内容count=1
while read line;doecho "Line #$count: $line"count=$[ $count + 1 ]
done	
exec 0>&6 #第一步的反操作,意思就是向STDIN写入就是向 fd6写入。如果不把STDIN 换回给控制台,read将读取不到内容。# -p 代表将read输入的参数 允许你在read命令行指定提示符
read -p "are you ok?" answer #测试STDIN是否恢复正常,如果不还原fd 0也就是STDIN,read就一直读取不到任何东西
case $answer inY|y) echo "i am OK" ;;N|n) echo "sorrsy i am sad";;
esac		

6、exec创建读写文件描述符

shell会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始。
案例参考1

7、关闭exec创建的文件描述符

&- 关闭fd

一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则shell会生成错误消息。

多个fd打开了同一个输出文件,后者覆盖前者的内容。

8、如何确定fd 已被使用 

lsof命令会列出整个Linux系统打开的所有文件描述符。这是个有争议的功能,因为它会向非系统管理员用户提供Linux系统的信息。所以许多Linux系统隐藏了该命令。

我所使用的ECS实例的lsof在如下这个位置

lsof -a -p $$ -d 0,1,2,3,6

 $$ 当前进程的PID

-p : PID

-d : FD

-a: 其他两个选项的结果执行布尔AND运算

[root@ninesun ~]# exec 6> file
[root@ninesun ~]# 
[root@ninesun ~]# echo "hah" >&6
[root@ninesun ~]# cat file
hah
[root@ninesun ~]# 
[root@ninesun ~]# 
[root@ninesun ~]# lsof -a -p $$ -d 0,1,2,3,6
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
bash    1546070 root    0u   CHR  136,0      0t0       3 /dev/pts/0
bash    1546070 root    1u   CHR  136,0      0t0       3 /dev/pts/0
bash    1546070 root    2u   CHR  136,0      0t0       3 /dev/pts/0
bash    1546070 root    6w   REG  253,1        4 1052946 /root/file

STDIN、 STDOUT和STDERR关联的文件类型是字符型。因为STDIN、 STDOUT和STDERR文
件描述符都指向终端,所以输出文件的名称就是终端的设备名。 切可读可写

输出结果解释:

  • COMMAND 正在运行的命令名的前9个字符
  • PID 进程的PID
  • USER 进程属主的登录名
  • FD 文件描述符号以及访问类型( r代表读, w代表写, u代表读写)
  • TYPE 文件的类型( CHR代表字符型, BLK代表块型, DIR代表目录, REG代表常规文件)
  • DEVICE 设备的设备号(主设备号和从设备号)
  • SIZE 如果有的话,表示文件的大小
  • NODE 本地文件的节点号
  • NAME 文件名

本文发布于:2024-01-28 07:02:21,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17063965615651.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进阶   示例   重定向   高级   shell
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23