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

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

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

-n nchars

                    read returns after reading nchars characters rather than waiting for a complete

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

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

6、查看File


其中重点在于第三四行:

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

所以结果是1234.67890

d4989d424053474bb390fbaa01bb1d62.png此外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 > /tmp/file.txt
echo "---------------"
exec 1>&6 6>&-
echo "==============="

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


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


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


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


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


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

30cfbb9bc7ee4fc0bbfedde51903b038.png

再看个例子加深下印象


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

0f5508d16a804c9ab1f51a5f24c1e543.png

exec打开fd1 并指向file.txt


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


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


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


输入w在file.txt查看。


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]'


脚本执行的结果


脚本以及结果的解释:

ba774e194cf84441aed6839dfed49e7d.png

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、多进程控制


e2830d82bf134c419372a26269102fe6.png

xargs 多进程示例

redirect]# time bash -c 'echo -e "1\n2\n3\n4" | 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 "1\n2\n3\n4" | 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);do
        echo
done >&5
# 多进程工作点
while true;do
        # 从命名管道中读一个空行
        read -u5
        date +"%T"
        { sleep 3s; echo >&5; } & #每次执行循环体内的内容进程少一个,需要使用echo >&5向进程池注入一个进程
done;
wait #父进程中等待子进程执行完

解释:

mkfifo $tempfifo ;创建于一个命名管道

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

运行结果

]# sh -x test2.sh 

acb6a945957548fc94ffa61e4828d9b6.png

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

4efc7c51372944c8a4f8332b73f9b9f0.png

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

600175142be1466db1b3ad7b3bbd57a0.png

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

# 从命名管道中读一个空行
  read -u5
  date +"%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

echo "aaahhe" >&3

d48636ee6c8a4cd6bc0b1ab7b49369e3.png

输入重定向


#!/bin/bash
exec 6<&0 # 开启fd 6,并指向STDIN。此时从fd6 中读取数据相当于从STDIN中读取
exec 0< testfile # 让STDIN从testfile读取内容
count=1
while read line;do
  echo "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 in
  Y|y) echo "i am OK" 
    ;;
  N|n) echo "sorrsy i am sad"
    ;;
esac    

cf08786152a24f5b9229d64ae65d021d.png

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


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

案例参考1

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


&- 关闭fd


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


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

40a74bc6295c4fc28011a7f0f27f1a5c.png

8、如何确定fd 已被使用


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


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

6b89d029873b45e3b6dc92d0530107df.png

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 文件名

目录
相关文章
|
7月前
|
Shell 数据处理
Shell编程中,输入/输出重定向和管道
Shell编程中,输入/输出重定向和管道
52 2
|
监控 Shell Linux
Linux Shell高级用法:优化和自动化你的工作流程
Linux Shell是一个非常强大的工具,可以用于自动化任务、处理文本和数据、进行系统管理等。在这篇文章中,我们将介绍一些Linux Shell的高级用法,帮助你更高效地利用Shell完成各种任务。
203 0
|
Shell Linux
Linux Shell 进阶:探索高级命令和脚本编程技巧
Linux Shell不仅仅是一个命令解释器,它还提供了许多强大的高级命令和脚本编程技巧,能够帮助用户更高效地管理系统和处理数据。在这篇文章中,我们将深入探讨Linux Shell的高级功能。
247 0
|
7月前
|
数据挖掘 Shell
在Shell中,标准输出重定向
在Shell中,标准输出重定向
75 1
|
2月前
|
Unix Shell Linux
Shell 输入/输出重定向
10月更文挑战第4天
30 8
|
6月前
|
Unix Shell Linux
Shell 重定向:控制数据流向的艺术
在Unix/Linux中,Shell提供输入输出重定向来灵活控制数据流。了解和运用重定向能提升Shell效率。标准输入(0)、输出(1)和错误输出(2)是基础。`&gt;`用于覆盖输出,`&gt;&gt;`用于追加,而`&lt;`用于改变输入源。错误输出可单独重定向,如`2&gt;`或`2&gt;&gt;`。组合使用如`2&gt;&1`可合并输出和错误到同一文件。输入重定向示例:`cat &lt;&lt;END`读取直到`END`。掌握这些,可高效管理命令输出。
58 0
|
Shell Linux
shell脚本学习-进阶
shell脚本学习-进阶
70 0
|
7月前
|
Shell
shell 命令(一)概述【别名、 bash重定向 、定义变量】
shell 命令(一)概述【别名、 bash重定向 、定义变量】
64 0
|
7月前
|
Unix Shell Linux
在Unix/Linux Shell中,管道(`|`)和重定向
在Unix/Linux Shell中,管道(`|`)和重定向
101 1