重定向的三种常见方式是什么 (重定向最佳方法)

目录

1、简单示例

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

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

4、多进程控制

1、简单示例

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

重定向怎么测试,重定向的三种常见方式是什么

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

分析一下

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

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

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

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

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

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

6、查看File

其中重点在于第三四行:

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

所以结果是1234.67890

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

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

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

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

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的还原。

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

再看个例子加深下印象

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

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

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

重定向怎么测试,重定向的三种常见方式是什么

脚本执行的结果

重定向怎么测试,重定向的三种常见方式是什么

重定向怎么测试,重定向的三种常见方式是什么

​ 脚本以及结果的解释:

exec 3<> /tmp/${0}${$}.tempexec 开启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 "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

redirect]# time bash -c 'echo -e "1\n2\n3\n4" | xargs -i -n 1 sleep {}' 

看一个多进程的例子:

#!/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 read line;do
	# 从命名管道中读一个空行
	read -u5
	date +"%T"
	{ sleep $line; echo >&5; } & #每次执行循环体内的内容进程少一个,需要使用echo >&5向进程池注入一个进程
done < /opt/shellTest/redirect/seq.txt;

wait #父进程中等待子进程执行完
exec 5>&-

  { sleep $line; echo >&5; } & #每次执行循环体内的内容进程少一个,需要使用echo >&5向进程池注入一个进程

解释:

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

运行结果

]# sh -x test2.sh 
+ trap 'kill 0;exit 1' SIGINT SIGTERM
+ proc_count=5
+ tempfifo=/tmp/temp_test2.sh_1534095.fifo
+ mkfifo /tmp/temp_test2.sh_1534095.fifo
+ exec
+ rm -rf /tmp/temp_test2.sh_1534095.fifo
++ seq 1 5
+ for i in $(seq 1 $proc_count)
+ echo
+ for i in $(seq 1 $proc_count)
+ echo
+ for i in $(seq 1 $proc_count)
+ echo
+ for i in $(seq 1 $proc_count)
+ echo
+ for i in $(seq 1 $proc_count)
+ echo
+ read line
+ read -u5
+ date +%T
16:18:11
+ read line
+ read -u5
+ date +%T
+ sleep 1
16:18:11
+ read line
+ read -u5
+ date +%T
+ sleep 2
16:18:11
+ read line
+ read -u5
+ date +%T
+ sleep 3
16:18:11
+ read line
+ read -u5
+ date +%T
16:18:11
+ read line
+ read -u5
+ sleep 4
+ sleep 5
+ echo
+ date +%T
16:18:12
+ read line
+ read -u5
+ sleep 6
+ echo
+ date +%T
16:18:13
+ read line
+ read -u5
+ sleep 7
+ echo
+ date +%T
16:18:14
+ read line
+ read -u5
+ sleep 8
+ echo
+ date +%T
16:18:15
+ read line
+ read -u5
+ sleep 9
+ echo
+ date +%T
16:18:16
+ read line
+ wait
+ sleep 10
+ echo
+ echo
+ echo
+ echo
+ echo
+ exec

+ tempfifo=/tmp/temp_test2.sh_1534095.fifo

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

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

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