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

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

我试图在 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 上写入的内容,您需要将lsstdout 和catstdin 作为 IPC 机制(如管道、套接字对或伪终端对)的两端。例如,lsstdout 是管道的写入端,catstdin 是管道的读取端。

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

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

此外,只有yash一个原始接口,pipe()您需要创建第二个管道以从lsstdout 读取(由构造创建的 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"

相关内容