如果进程生成了 xclip 的后台分支,则输出重定向将挂起

Output redirection hangs if process has spawned a background fork of xclip

我有一个在内部调用 xclip 的脚本。像这样:

#!/bin/bash
RESULT="some data"
echo $RESULT | xclip
echo $RESULT

xclip puts 将数据从其 stdin 放入剪贴板,生成一个后台线程以保留该缓冲区(这就是剪贴板在 X11 中的工作方式)并将其与 tty 分离。

如果我直接 运行 脚本,它会按预期工作:

$ ./script.sh
some data
$

但是,如果我尝试将它的输出传递到管道中,它将挂起直到 xclip 后台进程结束(基本上,直到其他人将数据放入剪贴板)。

$ ./script.sh | ./another_script.sh # it hangs

我发现 xclip 有一个 -f 标志似乎可以解决这个问题。

来自手册页:

when xclip is invoked in the in mode with output level set to silent (the defaults), the filter option will cause xclip to print the text piped to standard in back to standard out unmodified

但是,我试图理解为什么它会那样工作。

我创建了另一个行为相同的示例:

echo $(sleep 5 &)

此外,有人告诉我,在 fish 中,我的示例在没有 -f 标志的情况下工作。

所以,问题是:
1. 这是 shell(bashzsh)的错误还是预期的行为?
2. -f 标记如何影响该行为?从手册页看,似乎关系不大。
3. 如何使其他脚本(例如 sleep)按照 xclip-f 标志一起工作的方式工作?

默认情况下,

  1. 所有打开的文件都从父进程传递到子进程。和
  2. 创建管道 (A->B) 时,进程 B 不会在标准输入上获得 EOF 指示符,直到步骤 A 中的所有进程关闭其 STDOUT。

结合以上,当你执行/script.sh | ./another_script.sh时,后台的xclip会继承'script.sh' stdout(即./another_script.sh的输入),此时,有两个进程(script.sh,xclip)连接到管道。只有当两者都将关闭 STDOUT(或将终止)时,./another_script.sh 才会在管道上看到 EOF,并且能够退出(假设它正在等待输入的 EOF)。

这些情况的解决方案是让后台进程关闭标准输出,从而允许管道完成。通常,如果需要输出,stdout 将被重定向到 stderr。最有可能的是,'-f' 会关闭 STDOUT。

每个人 xclip,可以编写 echo $RESULT | xclip -f(不需要额外的回显)来避免这个问题,并允许脚本 运行 作为管道的一部分

man fork 和 man pipe 提供了详细信息。特别是

人 2 叉子:

  • The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two file descrip‐ tors share open file status flags, file offset, and signal-driven I/O attributes (see the description of F_SETOWN and F_SETSIG in fcntl(2)).

人 7 管:

If all file descriptors referring to the write end of a pipe have been closed, then an attempt to read(2) from the pipe will see end-of-file (read(2) will return 0).