Bash - 将输出重定向到变量或文件描述符,然后从变量或文件描述符中读取

Bash - 将输出重定向到变量或文件描述符,然后从变量或文件描述符中读取

以下是 bash 脚本的示例,它将所有输出重定向到文件(并也在屏幕上显示输出):

# writing to it
exec > >(tee --ignore-interrupts ./log)
exec 2>&1

echo "here is a message"

# reading from it again
cat ./log

现在我不想创建该文件./log。我想将所有标准输出保留在内存中,并能够稍后在脚本中再次读取它。另外,我所处的情况可能没有安装根文件系统。

我尝试使用进程替换而不是 来执行此操作./log,但我似乎无法理解如何传递由后续命令的进程替换创建的文件描述符,以便读取我刚刚编写的内容。

这是另一个例子:

# can I make ./log a temporary file descriptor, an in-memory buffer?
exec 3>./log 4<./log
# writes
echo >&3 "hello"
# reads
cat <&4

答案1

如果您想全局重定向所有写入的内容(就像您现在所做的那样),这很棘手,但可以组合在一起。

我强烈建议,如果可能的话,只需使用普通管道即可。只需将您所做的所有事情包装在子外壳中即可。在这种情况下

(
 echo "this is the message"
 other stuff
) | cat

或者只是使用“$()”语法将所有内容写入变量。

下一个方法是使用您所做的,但写入 tmpfs 或者/dev/shm如果它们可用。这非常简单,但是您必须知道有哪些基于 ram 的文件系统(如果可能的话,并设置它们)。

另一种方法是使用 来创建 fifo mkfifo。在这两种情况下,您都需要自行清理。

编辑:

我有一个非常丑陋的黑客,但我打赌有人可以改进它。

#!/bin/bash
exec 3>&1
exec > >( tee >( ( tac && echo _ ) | tac | (read && cat > ./log) ) )

echo "lol"
sleep 5
echo "lol"

echo "finished writing"

exec >&-
exec >&3
exec 3>&-
echo "stdout now reopen"
sleep 1 #wait if the file is still being written asynchronously
cat ./log

它是如何工作的:首先,你有一个,tee这样你就可以看到发生了什么。这又输出到另一个进程替换。在那里,您有一个技巧tac|tac(因为tac需要整个输入才能开始输出)等待整个流完成然后再继续。最后一部分位于子 shell 中,该子 shell 实际上将其输出到文件中。当然,最终的 shell 将在实例化后立即在文件系统中创建输出文件(如果这是唯一的行)。因此,必须首先完成一些等待输入最终到来的事情,以延迟文件创建。我通过首先使用 echo 输出一条虚拟行,然后读取并丢弃它来实现此目的。这些read块会一直阻塞,直到您关闭文件描述符为止,这表明tac它的时间已经到了。因此,最后关闭 stdout 文件描述符。我还在打开进程替换之前保存了原始文件stdout,以便在最后恢复它(cat再次使用)。那里有一个sleep 5,所以我可以检查ls该文件是否真的不是太早创建的。最后的睡眠比较棘手... subshel​​l 是异步的,如果有大量输出,您将等待两个tacs 完成其工作,然后文件才真正存在。因此,合理地,您可能需要做其他事情来检查事情是否真的完成了。例如,&& touch sentinel在最后一个子 shell 的末尾,然后while [ ! -f sentinel ]; do sleep 1; done && rm sentinel在最终使用该文件之前。

总而言之,有两个进程替换,并且在内部有两个子壳和 2 个管道。这是我写过的最丑陋的东西之一......但它应该仅在您关闭标准输出时创建文件,这意味着它可以很好地控制并且可以在文件系统准备好时完成。

答案2

简单案例:

output="$(echo "here is a message")"
# [...]
echo "$output"

如果您必须从重定向中读取并且不能使用像这样的管道,那么就不那么简单了

echo "$output" | while IFS= read -r line; do :; done

您可以使用 FIFO 和后台进程:

mkfifo fifo
echo "$output" >fifo &
while IFS= read -r line; do :; done <fifo

T 恤版本

mkfifo fifo
( output="$(cat fifo)"; echo "$output" >fifo ) &
exec 3>&1
exec >fifo
...
exec 1>&3
cat fifo

相关内容