使用 shell 重定向读取/写入同一文件描述符

Tom*_*ale 5 shell io-redirection shell-script

我试图在 shell 重定向的上下文中理解文件描述符。

为什么我无法读取由的 STDOUTcat写入的 FD 3 ?ls

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1;
Run Code Online (Sandbox Code Playgroud)

当尝试这个时,cat仍然想从我的键盘上读取。

如果这不能做到,为什么不呢?


区分:本题是关于读/写同一个文件描述符,以Redirect STDERR and STDOUT to different Variables without tempor files 为例。

Sté*_*las 3

\n
\n
{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1\n
Run Code Online (Sandbox Code Playgroud)\n
\n

{ ... } 3>&1fd 1 克隆到 fd 3。这仅意味着 fd 3 现在指向与 fd 1 指向的相同资源(相同的打开文件描述)。如果您从终端运行它,那可能是一个以读+写模式打开到终端设备的 fd。

\n

之后exec 0<&3,fds 0、1 和 3 都指向相同的打开文件描述(当终端仿真器打开它在执行 shell 之前创建的伪终端对的从属端(在终端中运行命令的情况下)时创建)上述情况)。

\n

然后在 中,对于执行更改的out=$(cat)进程,fd 1 为管道的写入端,而 0 仍然是 tty 设备。所以cat$(...)cat将从终端设备读取,因此您在键盘上输入的内容(如果它不是终端设备,您可能会收到错误,因为 fd 可能以只写模式打开)。

\n

为了cat读取ls其 stdout 上写入的内容,您需要将lsstdout 和catstdin 作为 IPC 机制(如管道、套接字对或伪终端对)的两端。例如lsstdout 是管道的写入端并且cat stdin 是管道的读取端。

\n

但你还需要lscat,而不是一个接一个地运行,因为这是一种 IPC(进程间通信)机制。

\n

由于管道可以保存一些数据(在当前版本的 Linux 上默认为 64 KiB),如果您设法创建第二个管道,您将获得短输出,但对于较大的输出,您会遇到死锁,ls当管道已满并且会挂起,直到有东西清空管道,但cat只能清空管道ls

\n

另外,只有yash一个原始接口pipe(),您需要创建第二个管道以从 stdout 读取ls(stderr 的另一个管道由$(...))。

\n

在 yash 中,你会这样做:

\n
{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4\n
Run Code Online (Sandbox Code Playgroud)\n

在哪里3>>|4(yash 特定功能)创建第二个管道,写入端位于 fd 3 上,读取端位于 fd 4 上。

\n

但同样,如果 stderr 输出大于管道的大小,则会挂起。我们有效地将管道用作内存中的临时文件,而不是管道。

\n

要真正使用管道,我们需要开始lsstdout 作为一个管道的写入端,将 stderr 作为另一个管道的写入端,然后 shell 在数据到来时同时读取这些管道的另一端(不是一个管道)。在另一个或再次你会遇到死锁)存储到两个变量中。

\n

为了能够在数据到来时读取这两个 fd,您需要一个带有select()/poll()支持的 shell。zsh就是这样一个 shell,但它没有yash管道重定向功能\xc2\xb9,因此您需要使用命名管道(因此管理它们的创建、权限和清理)并使用zselect/的复杂循环sysread ..的复杂循环。 。

\n

\xc2\xb9 如果在 Linux 上,您将能够使用/proc/self/fd/x管道上的行为类似于命名管道的事实,因此您可以这样做:

\n
#! /bin/zsh -\nzmodload zsh/zselect\nzmodload zsh/system\n\n(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)\n(){exec {we}>$1 {re}<$1} <(:)\n\nls -d / /x >&$wo 2>&$we &\nexec {wo}>&- {we}>&-\nout= err=\no_done=0 e_done=0\n\nwhile ((! (o_done && e_done))) && zselect -A ready $ro $re; do\n  if ((${#ready[$ro]})); then\n    sysread -i $ro && out+=$REPLY || o_done=1\n  fi\n  if ((${#ready[$re]})); then\n    sysread -i $re && err+=$REPLY || e_done=1\n  fi\ndone\n
Run Code Online (Sandbox Code Playgroud)\n