我试图在 shell 重定向的上下文中理解文件描述符。
为什么我无法读取由的 STDOUTcat
写入的 FD 3 ?ls
{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1;
当尝试这个时,cat
仍然想从我的键盘上读取。
如果这不能做到,为什么不呢?
区分:这个问题是关于读/写同一个文件描述符,使用提出的问题将 STDERR 和 STDOUT 重定向到没有临时文件的不同变量 举个例子。
答案1
在
{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1
将 fd 1克隆{ ... } 3>&1
到 fd 3。这仅意味着 fd 3 现在指向相同的资源(相同的资源)打开文件描述)正如 fd 1 所指向的那样。如果您从终端运行它,那可能是一个以读+写模式打开到终端设备的 fd。
之后exec 0<&3
,fds 0、1、3都指向同一个打开文件描述(当您的终端模拟器打开它在执行 shell 之前创建的伪终端对的从属端(在上面的终端情况下运行命令的情况下)时创建)。
然后在 中,对于执行更改的out=$(cat)
进程,fd 1 为管道的写入端,而 0 仍然是 tty 设备。因此将从终端设备读取,因此您在键盘上输入的内容(如果它不是终端设备,您可能会收到错误,因为 fd 可能以只写模式打开)。cat
$(...)
cat
为了cat
读取ls
其 stdout 上写入的内容,您需要将ls
stdout 和cat
stdin 作为 IPC 机制(如管道、套接字对或伪终端对)的两端。例如,ls
stdout 是管道的写入端,cat
stdin 是管道的读取端。
但您还需要同时运行ls
和cat
,而不是一个接一个地运行,因为这是一种 IPC(进程间通信)机制。
由于管道可以抓住一些数据(当前版本的 Linux 上默认为 64 KiB),如果您设法创建第二个管道,您将获得短输出,但对于较大的输出,您会遇到死锁,ls
当管道已满时会挂起,并且会挂起直到有东西清空管道,但cat
只能在返回时清空管道ls
。
此外,只有yash
一个原始接口,pipe()
您需要创建第二个管道以从ls
stdout 读取(由构造创建的 stderr 的另一个管道$(...)
)。
在 yash 中,你会这样做:
{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4
其中3>>|4
(yash 特定功能)创建第二个管道,写入端位于 fd 3 上,读取端位于 fd 4 上。
但同样,如果 stderr 输出大于管道的大小,则会挂起。我们有效地将管道用作内存中的临时文件,而不是管道。
要真正使用管道,我们需要首先ls
将 stdout 作为一个管道的写入端,将 stderr 作为另一个管道的写入端,然后 shell 在数据到来时同时读取这些管道的另一端(不是一个管道)。在另一个或再次你会遇到死锁)存储到两个变量中。
为了能够在数据到来时读取这两个 fd,您需要一个带有select()
/poll()
支持的 shell。zsh
就是这样一个shell,但是它没有yash
的管道重定向功能 1,因此您需要使用命名管道(因此管理它们的创建、权限和清理)并使用带有zselect
/ sysread
... 的复杂循环
¹ 如果在 Linux 上,您将能够使用/proc/self/fd/x
管道上的行为类似于命名管道的事实,因此您可以执行以下操作:
#! /bin/zsh -
zmodload zsh/zselect
zmodload zsh/system
(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)
(){exec {we}>$1 {re}<$1} <(:)
ls -d / /x >&$wo 2>&$we &
exec {wo}>&- {we}>&-
out= err=
o_done=0 e_done=0
while ((! (o_done && e_done))) && zselect -A ready $ro $re; do
if ((${#ready[$ro]})); then
sysread -i $ro && out+=$REPLY || o_done=1
fi
if ((${#ready[$re]})); then
sysread -i $re && err+=$REPLY || e_done=1
fi
done
答案2
这似乎是变量赋值语法的基本限制,以及 shell 生成子 shell 的副作用。您可以捕获 stderr 或 stdout,但不能同时捕获两者:另一个流需要重定向到文件(可能是 FIFO)。
# a function for testing
your_command() { sh -c 'echo "this is stdout"; echo "this is stderr" >&2'; }
errfile=$(mktemp)
out=$( your_command 2>|"$errfile" )
err=$(< "$errfile")
rm "$errfile"
echo "out: $out"
echo "err: $err"