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 为例。
在
\n\n\nRun Code Online (Sandbox Code Playgroud)\n{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1\n
将{ ... } 3>&1
fd 1 克隆到 fd 3。这仅意味着 fd 3 现在指向与 fd 1 指向的相同资源(相同的打开文件描述)。如果您从终端运行它,那可能是一个以读+写模式打开到终端设备的 fd。
之后exec 0<&3
,fds 0、1 和 3 都指向相同的打开文件描述(当终端仿真器打开它在执行 shell 之前创建的伪终端对的从属端(在终端中运行命令的情况下)时创建)上述情况)。
然后在 中,对于执行更改的out=$(cat)
进程,fd 1 为管道的写入端,而 0 仍然是 tty 设备。所以cat
$(...)
cat
将从终端设备读取,因此您在键盘上输入的内容(如果它不是终端设备,您可能会收到错误,因为 fd 可能以只写模式打开)。
为了cat
读取ls
其 stdout 上写入的内容,您需要将ls
stdout 和cat
stdin 作为 IPC 机制(如管道、套接字对或伪终端对)的两端。例如ls
stdout 是管道的写入端并且cat
stdin 是管道的读取端。
但你还需要ls
和cat
,而不是一个接一个地运行,因为这是一种 IPC(进程间通信)机制。
由于管道可以保存一些数据(在当前版本的 Linux 上默认为 64 KiB),如果您设法创建第二个管道,您将获得短输出,但对于较大的输出,您会遇到死锁,ls
当管道已满并且会挂起,直到有东西清空管道,但cat
只能清空管道ls
。
另外,只有yash
一个原始接口pipe()
,您需要创建第二个管道以从 stdout 读取ls
(stderr 的另一个管道由$(...)
)。
在 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 上。
但同样,如果 stderr 输出大于管道的大小,则会挂起。我们有效地将管道用作内存中的临时文件,而不是管道。
\n要真正使用管道,我们需要开始ls
stdout 作为一个管道的写入端,将 stderr 作为另一个管道的写入端,然后 shell 在数据到来时同时读取这些管道的另一端(不是一个管道)。在另一个或再次你会遇到死锁)存储到两个变量中。
为了能够在数据到来时读取这两个 fd,您需要一个带有select()
/poll()
支持的 shell。zsh
就是这样一个 shell,但它没有yash
的管道重定向功能\xc2\xb9,因此您需要使用命名管道(因此管理它们的创建、权限和清理)并使用zselect
/的复杂循环sysread
..的复杂循环。 。
\xc2\xb9 如果在 Linux 上,您将能够使用/proc/self/fd/x
管道上的行为类似于命名管道的事实,因此您可以这样做:
#! /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
归档时间: |
|
查看次数: |
1520 次 |
最近记录: |