自定义 Git 命令在执行 git diff 后死于信号 13 (SIGPIPE)

Custom Git command dies from signal 13 (SIGPIPE) after exec'ing git diff

如果您在 PATH 中创建名为 git-mydiff 的 shell 脚本,其中包含:

#!/bin/bash
exec git diff

并在修改量较大的版本库中调用git mydiff,退出pager时,会输出:

error: git-mydiff died of signal 13

但是如果直接执行path/to/git-mydiff,退出pager时不会报错

显然,一种解决方案是不使用 exec,但为什么这是个问题?为什么只有通过 git 代理命令调用脚本时才会出现问题?

我正在使用:git 2.5.4 版(Apple Git-61)

您的程序(在本例中为 mydiff)和寻呼机(less,或您选择的任何 core.pager)通过管道连接。 OS 在 reader 必须清理一些之前可以写入管道的数据量有一些限制,并且寻呼机在暂停之前不会读取整个管道,所以在一定数量的输出,管道已满,您的程序在其 write 系统调用中被阻塞。

如果管道的读取端消失(通过让寻呼机退出),此时会发生两件事:OS 向您的程序传递一个 SIGPIPE 信号,并且 OS 的 write 系统调用失败并出现 EPIPE 错误。通常第一个——信号——会在第二个发生之前杀死你的程序,但如果你的程序要捕获或忽略 SIGPIPE,第二个就会发生。

这是 SIGPIPE 的作业控制 shell 中的一个示例,它终止了一个进程:

> cat book.pdf | : &
>
[1]    Broken pipe                   cat book.pdf |
       Done                          :

(顺便说一句 : 这里是内置的冒号命令,它是一个空操作;我认为它是 Mashey shell 的遗留物 goto 作为一个外部程序。)运行 这是一个常规的前台进程,序列是无声的:

> cat book.pdf | :
>

这是因为 shell 不会抱怨死于 SIGPIPE 进程,因为 "died of SIGPIPE" 很正常。

无论出于何种原因,git 前端对这个死于 SIGPIPE 的案例更加嘈杂。如果不用exec,就是shell看到死的-SIGPIPE。 shell 安静地吸收并干净地退出并且 git 不抱怨。如果你使用exec,shell被你的程序替换,git前端命令看到死的-SIGPIPE状态和抱怨。

一个明显的治疗方法是将 shell 留在附近。另一种方法是让 shell 到 ignore(不捕获)SIGPIPE,然后执行 exec:

trap "" PIPE

如你catching SIGPIPE不好:因为exec替换了地址[=94=的当前占用者],OS 将所有 caught 信号重置为其默认配置(对于 SIGPIPE 是 "die of signal")。但是,ignored 信号仍然被忽略。

根据您的程序,这可能同样糟糕或更糟。例如,当 cat 死于 SIGPIPE 时,shell 是沉默的,但是当 cat 看到 writeEPIPE 而失败时,它会抱怨:

$ cat book.pdf | :
$ trap "" PIPE
$ cat book.pdf | :
cat: stdout: Broken pipe

(这是在 FreeBSD 上;不同的 OS 会略有不同,这取决于它们的实用程序有多谨慎和聪明)。