SIGPIPE 由于文件描述符和进程替换

SIGPIPE due to file descriptors and process substitution

我正在尝试从执行工具的 bash 函数中保存一些日志(其中一些 运行 在子 shell 中)。此外,我想将所有错误打印到终端。

我的代码在按下 ctr-c 和一个奇怪的日志文件时导致一个 sigpipe 和退出代码 141。管道故障似乎是由于在陷阱内将 stdout 重定向到 stderr,这会破坏 tee 命令的 stdout 流。有趣的是,代码按预期终止,退出代码为 130,没有在陷阱或 cat 命令中使用重定向。

  1. 我仍然无法修复和解释生成的日志文件。为什么有些回声两次,为什么trap回声也写入文件?

  2. 为什么sigpipe不是由函数内的重定向引起的?

trap '
echo trap_stdout
echo trap_stderr >&2
' INT

fun(){
    echo fun_stdout
    echo fun_stderr >&2
    ( sleep 10 | cat )
}

echo > log
fun >> log 2> >(tee -a log)

日志文件

fun_stdout
fun_stderr
fun_stderr
trap_stdout

编辑:根据 oguz ismail answer

的工作示例
exec 3>> log
exec 4> >(tee -ai log >&2)
fun 2>&4 >&3
exec 3>&-
exec 4>&-

SIGPIPE 的来源是 SIGINT(由 ctrl/c 发起)被发送到所有进程 运行ning:“主”bash 进程(执行 'fun' 函数),子 shell 执行 'tee -a'。结果,在 Ctrl/C,两人都被杀了。当主进程试图发送 'trap_stderr' 到 te“tee”进程时,它得到 SIGPIPE,因为“tee”已经死了。

考虑到 'tee -a' 的作用,保护它免受 SIGINT 的影响并允许它 运行 直到 'fun' 完成(或被杀死)是有意义的。考虑对最后一行的以下更改

fun >> log 2> >(trap '' INT ; tee -a log >&2)

这将生成日志文件:

Console (stderr)
fun_stderr
^Ctrap_stderr

Log File: (no duplicates)

fun_stdout
fun_stderr
trap_stdout
trap_stderr

以上内容还将解决第二个问题,即日志文件中的重复行。这是使用 tee 将每个 stderr 行发送到日志文件和 stdout 的结果。鉴于 stdout 刚刚(通过“>>log”)重定向到 'log' 文件,输出的两个副本都被发送到日志文件,none 到终端

鉴于重定向是按顺序执行的,更改 'tee' 行以将输出发送到原始标准错误(而不是已经重定向的标准输出)将在终端(或任何标准错误)上显示输出

Why are there some echos twice

fun 的标准输出在其标准错误被重定向到为 tee 创建的 FIFO 之前被重定向到 log,因此 tee 继承了一个被重定向到的标准输出log。我可以这样证明:

$ : > file 2> >(date)
$ cat file
Sat Jul 25 18:46:31 +03 2020

更改重定向的顺序将解决这个问题。例如:

fun 2> >(tee -a log) >> log

and why are the trap echos written to the file as well?

如果在 shell 仍在执行 fun 时触发了为 SIGINT 设置的陷阱,则与 fun 关联的重定向生效是完全正常的。

要将陷阱操作的标准输出和标准错误连接到主要 shell 的标准输出和标准错误,您可以这样做:

exec 3>&1 4>&2

handler() {
  : # handle SIGINT here
} 1>&3 2>&4

trap handler INT

或类似的东西;这个想法是复制主要 shell 的标准输出和标准错误。

Why isn't the sigpipe caused earlier by the redirection within the function?

因为tee在执行echo fun_stderr >&2时还活着。并且 sleep 不会向其标准输出写入任何内容,因此它无法触发 SIGPIPE。

此脚本因 SIGPIPE 而终止的原因是 tee 也接收到键盘生成的 SIGINT,并在执行与 SIGINT 关联的陷阱操作之前终止。结果,在执行 echo trap_stderr >&2 时,由于其 stderr 连接到刚才关闭的管道,shell 接收到 SIGPIPE。

为了避免这种情况,正如已经建议的那样,您可以使 tee 忽略 SIGINT。不过,您不需要为此设置空陷阱,-i 选项就足够了。

fun 2> >(tee -a -i log) >> log