文件描述符暂存内容

使用文件描述符暂存内容,使用完成后释放

# 背景

shell 脚本时,有时会需要保存临时数据,供脚本后续流程使用的场景; 之前是在执行脚本时生成临时文件,脚本执行完成后删除文件

# 几种实现方式

# 使用 mktemp 生成临时文件

如下示例:

1
2
3
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
: ...
rm "$tmpfile"

# mktemp 结合 文件描述符使用

可以通过在脚本中打开文件的文件描述符后将其删除,来确保当脚本退出(包括终止和崩溃)时删除文件。
只要文件描述符未关闭,该文件就一直可用(只对当前脚本而言,不是真的对于所有的其他进程)。当文件描述符关闭时(内核在进程退出时自动执行),文件系统将删除该文件。
如下示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建临时文件
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)

# 给临时文件创建文件描述符 3,完成后即可执行类似 echo ... >&3 之类的命令来写该临时文件
exec 3>"$tmpfile"

# 给临时文件创建文件描述符 4 以从同一文件中读取内容,以便文件的读写位置可以不同
exec 4<"$tmpfile"

# 删除临时文件; 目录被立即删除,在关闭文件描述符后,inode 的引用计数才会递减
# 当引用计数降为零后,文件内容块被释放(这是真正的删除)
rm "$tmpfile"

# 脚本内容
: ...

# 写入数据到文件描述符 3 中
echo foo >&3

# 脚本内容
: ...

# 从读文件描述符中读取数据
head -n 1 <&4

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

另一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ cat test.sh 
#!/bin/bash
tmpfile=$(mktemp)
exec 3>$tmpfile
exec 4<$tmpfile
rm -f $tmpfile

## Note that file descriptor always concatenates, not overwrites
echo "# set test1 #"
echo 'test1' >&3
echo "# set test2 #"
echo 'test2' >&3

echo "# cat all from fd 3#"
cat <&4

## stack 
echo "# set value for fd 3#"
echo -e 'test1\ntest2\ntest3\ntest4\ntest5' >&3
echo "# get value from fd 3 line 1 #"
head -1 <&4
echo "# get value from fd 3 line 1 #"
head -1 <&4

echo "# cat all from fd 3#"
cat <&4

$ bash test.sh 
# set test1 #
# set test2 #
# cat all from fd 3#
test1
test2
# set value for fd 3#
# get value from fd 3 line 1 #
test1
# get value from fd 3 line 1 #
test2
# cat all from fd 3#
test3
test4
test5

# fd 的使用场景

当要依次写入多个文件时,使用显式文件描述符会比较有用。
例如,完成一个脚本,数据保存要求有:

1.将数据输出到数据保存文件
2.将数据记录到日志文件
3.错误消息保存到错误日志文件

如上意味着脚本需要有三个输出通道:一个用于数据,一个用于日志,一个用于错误。由于输出只有两个标准描述符,因此需要打开第三个文件描述符。
打开文件描述符可以调用 exec

1
2
3
4
5
6
7
8
exec >data-file
exec 3>log-file
echo "first line of data"
echo "this is a log line" >&3
if something_bad_happens; then echo error message >&2; fi
exec >&-  # close the data output file
echo "output file closed" >&3

# 其他

# 一些概念

FIFO 和 mkfifo
FIFOfirst in, first out 的意思。
先进先出 (FIFO) 文件称为命名管道(named pipe),和将其他命令的输入和输出联系在一起的常规管道一样,FIFO 也是管理进程之间的输入和输出。但任何进程都可以(如果文件系统权限允许)从常规管道读取和写入,这允许多个进程相互通信,甚至不知道谁在接收它们发送的数据(或者发送它们正在接收的数据)。mkfifo 命令用于生成一个 FIFO,可以选择设置权限来规避这种情况。

如下示例:

1
2
3
4
5
6
7
8
## 创建 FIFO 特殊文件 fifo1 和 fifo2
$ mkfifo fifo1 fifo2

## 创建 FIFO 特殊文件 fifo1,并配置读写执行权限给所属者
$ mkfifo -m 700 myfifo

## 创建 FIFO 特殊文件 /dir1/dir2/fifo1,并且创建路径中不存在的每个目录
$ mkfifo -p /dir1/dir2/fifo1

fifo 文件使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
## 在命名管道 (fifos) 的上下文中,使用文件描述符可以启用非阻塞管道行为
(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

fifo buffer 有一定的大小限制,使用时尽量不要超过 65536 字节

# 参考内容

本文内容参考自

updatedupdated2022-06-132022-06-13