假设我有一个实用程序foo
可以同时写入n
不同的文件。每个文件中都会写入不同的数据。事先不知道写入文件的内容。例如,可以像这样调用该实用程序:
foo file_1 file_2 ... file_n
我想将写入文件的内容存储在 bash 变量中。当然,我可以让实用程序写入文件系统上的实际文件,然后从中读取:
foo file_1 ... file_n
output_1="$(cat file_1)"
...
output_n="$(cat file_n)"
rm file_1 ... file_n
但是,我认为跳过文件系统并避免创建临时文件可能会更有效。如何才能做到这一点?命名管道可以用于此目的吗?
答案1
我想我找到了答案。 @terdon 解决方案的问题在于,正如 @ilkkachu 提到的,它假设文件按顺序写入。它首先读取file1
直到完成,然后继续读取file2
。但是,可能会同时foo
写入file1
和,并且由于没有读取器附加到,因此它会阻塞。file2
file2
可以通过在调用后立即打开每个 fifo 进行读取foo
,然后按顺序读取它们来解决此问题。然而,这仅在foo
写入不超过 fifo 缓冲区大小时有效。
因此,每个文件/fifo 都需要同时读取。对于每个 fifo,我们可以创建一个在后台运行的子 shell,并从 fifo 中读取数据直至完成。然后子 shell 将内容打印到 stdout。因此,它充当写入 fifo 的数据的缓冲区。
# assume files don't exist yet
mkfifo "$@"
# opens fifos as writing
foo "$@" &
# for each fifo, create background subshell that reads from it
i=0
for file in "$@"; do
exec {fd}< <(echo "$(cat $file)")
fds[$i]=$fd
(( i++ ))
done
# fifos are now fully connected
# consume content buffered in subshells
i=0
for file in "$@"; do
file_contents[i]="$(cat <&${fds[$i]})"
(( i++ ))
done
for (( i=0; i<${#file_contents[@]}; i++)); do
printf "The contents of file number %d are: %s\n" "$(( $i+1 ))" "${file_contents[$i]}"
done
答案2
是的,这正是命名管道(也称为 FIFO)的用途。这是一个玩具示例:
#!/bin/bash
i=0
for file in "$@"; do
mkfifo "$file"
printf "This is file '%s'\n" "$file" > "$file" &
file_contents[i]=$(cat < "$file")
rm "$file"
(( ++i ))
done
for (( i=0; i<${#file_contents[@]}; i++)); do
printf "The contents of file number %d are: %s\n" "$i" "${file_contents[i]}"
done
这看起来像是您正在创建和删除文件,而且从形式上来说您确实是这样;但它们被命名为管道而不是常规的文件,因此实际上没有数据写入磁盘。
然而,这仍然使用文件系统,严格来说,它只是最少地使用它。该文件没有内容,因此实际上没有任何内容写入磁盘,但会为其创建一个文件系统条目。从man fifo
:
DESCRIPTION
A FIFO special file (a named pipe) is similar to a pipe, except that
it is accessed as part of the filesystem. It can be opened by multi‐
ple processes for reading or writing. When processes are exchanging
data via the FIFO, the kernel passes all data internally without writ‐
ing it to the filesystem. Thus, the FIFO special file has no contents
on the filesystem; the filesystem entry merely serves as a reference
point so that processes can access the pipe using a name in the
filesystem.
对于你的情况,你可以这样做:
#!/bin/bash
for file in "$@"; do
## CAREFUL: this will delete any files with the same name if they exist
if [ -e "$file" ]; then
rm -- "$file"
fi
mkfifo -- "$file"
done
## This is your utility
foo "$@" &
## And now read the variables
i=0
for file in "$@"; do
file_contents[i]=$(cat < "$file")
rm -- "$file"
(( ++i ))
done
## And here you can use the file_contents array to do whatever you need
for (( i=0; i<${#file_contents[@]}; i++)); do
printf "The contents of file number %d are: %s\n" "$i" "${file_contents[i]}"
done
要运行foo file1 file2 file3
你只需运行该脚本file1 file2 file3
作为其参数:
my_script.sh file1 file2 file3