Git标准错误输出无法进行管道传输。

13

我正在使用bash和zenity编写一个用于处理git://链接的图形URI处理程序,并且我正在使用zenity 'text-info'对话框在运行时显示git的克隆输出,使用FIFO管道。脚本大约有90行,所以我不会在此处贴出它,但以下是最重要的几行:

git clone "$1" "$target" 2>&1 | cat >> /tmp/githandler-fifo &
cat /tmp/githandler-fifo | zenity --text-info --text='Cloning git repository' &

我使用FIFO而不是直接管道,以允许它们异步运行并在关闭zenity窗口时允许终止git。

问题是,从git的输出中只显示第一行:

Initialized empty Git repository in /home/delan/a/.git/

其他计数对象的行在终端上不显示或者被显示了。

当前原因

目前大家认为这个问题出在cat是非阻塞的,在第一行之后就退出了,只把第一行传给了zenity而不是剩下的。我的目标是强制读取时阻塞,并使zenity的文本信息对话框逐步显示所有输出。

git会在stderr上输出进度信息(除了“Initialized”消息之外的任何内容),但是当我尝试将stderr管道传输到文件或与stdout合并时,这些消息就消失了。

修复尝试1

我尝试编写了两个阻塞版本的C语言的cat函数,即bread和bwrite,代码如下:

#include <stdio.h>
main(int argc, char **argv) {
    int c;
    for (;;) {
        freopen(argv[1], "r", stdin);
        while ((c = getchar()) != EOF)
            putchar(c);
    }
}

#include <stdio.h>
main(int argc, char **argv) {
    int c;
    for (;;) {
        freopen(argv[1], "w", stdout);
        while ((c = getchar()) != EOF)
            putchar(c), fputs("writing", stderr);
    }
}
它们的工作很好,因为它们会阻塞并且不会在EOF时退出,但这还没有完全解决问题。目前,在理论上使用一个、另一个或两者都可以实现,但实际上,zenity现在根本没有显示任何内容。
修复尝试2:
@mvds建议使用常规文件结合`tail -f`而不是`cat`来完成此操作。对于这样一个简单的解决方案感到惊讶(谢谢!),我尝试了一下,但不幸的是,只有第一行出现在zenity中,没有其他内容。
修复尝试3:
经过一些`strace`和检查`git`源代码,我意识到`git`将所有的进度信息(在“Initialized”消息之后的任何内容)输出到标准错误`stderr`上,并且这是第一行以及我认为是因为`cat`在EOF时早早退出所以导致这种情况的假设是巧合/误导的假设(`git`直到程序结束才会EOF)。
情况似乎变得简单多了,因为我不需要从原始代码(在问题开头)更改任何东西,它应该可以正常工作。然而,奇怪的是,当重定向时,标准错误输出会“消失”,这只发生在`git`中。
测试用例?尝试这个,看看文件中是否有任何内容(你不会看到任何东西):
git clone git://anongit.freedesktop.org/xorg/proto/dri2proto 2> hurr

这与我所知道的关于stderr和重定向的一切都不符;我甚至编写了一个小的C程序,以证明重定向对于git来说根本行不通。

尝试修复4

根据Jakub Narębski的回答以及我发送到git邮件列表的回复,--progress是我需要的选项。请注意,此选项仅在命令之后生效,而不是在clone之前。

成功!

非常感谢您的所有帮助。这是修复后的命令:

git clone "$1" "$target" --progress > /tmp/githandler-fifo 2>&1 &


1
天啊,你救了我的命...... 是否碰巧遇到同样的问题? - KnF
成功了!谢谢你。--progress 对我来说是缺失的拼图块。 - Form
3个回答

19

我认为至少有些进度报告在输出不是终端(tty)时会被静音处理。 我不确定它是否适用于您的情况,但请尝试在执行'git clone'(即使用git clone --progress <repository>)时传递--progress选项。

虽然我不知道这是否是您想要的。


我怀疑这可能就是答案。测试也很容易 - 只需将重定向到普通文件而不是FIFO,然后进行微调,直到您知道输出符合要求。 - Cascabel
不幸的是,git输出“未知选项:--progress”,然后换行,然后是语法/用法摘要。有趣的是,这一次,所有输出都在zenity上,并且FIFO正常工作。此外,git完全并且很好地输出到文件;可能是zenity的非阻塞读取导致了问题。 - Delan Azabani
@Delan:啊,不是这个。(关于--progress选项,可能意味着你使用的Git版本过旧-该功能在v1.7.1中添加。) - Cascabel
@Delan:实际上,我有点困惑,看着你的所有评论。你在这里说它可以成功地写入文件,但在其他地方你又说尝试重定向stderr时它就消失了。究竟是哪一个? - Cascabel
奇怪的是,我正在使用1:1.7.1-1.1(不应该太旧),但它仍然无法接受“--progress”。 - Delan Azabani
实际上,这是因为我把 --progress 放在 clone 前面,这样不起作用。如果在命令后面加上 --progress,就可以了,谢谢! - Delan Azabani

6

首先,输出重定向是从右到左进行解析的,因此

git clone "$1" "$target" 2>&1 > /tmp/githandler-fifo &

不等于
git clone "$1" "$target" > /tmp/githandler-fifo 2>&1 &

后者将stderr重定向到stdout,然后将stdout(包括stderr)重定向到文件。前者将stdout重定向到文件,然后在stdout上显示stderr。

至于管道输出到zenity(我不了解),我认为你可能会用命名管道使事情过于复杂。使用strace可以揭示您启动的进程的内部工作原理。对于没有经验的用户来说,与正常管道相比,命名管道会使情况变得更糟。


作为一个更简单的替代方案,您可以重定向到一个文件,然后再使用管道命令 tail -f。这样可以避免一些常见的命名管道问题。 - mvds
2
关于从右到左的重定向执行 - 这是完全错误的。重定向按自然顺序执行。重定向 2>&1 >file 的处理方式如下:在第一次重定向之后,fd 2 指向与 fd 1 相同的文件(即原来的 stdout,例如终端)。在第二次重定向之后,fd 1 指向 file,而 fd 2 仍然指向终端,因为重定向是在 fd 1 改变之前进行的。 - Roman Cheplyaka
@mvds,我已经尝试过将git重定向到常规文件并将tail -f管道传输到zenity,并且它可以通过第一行输出,但是后续的输出仍然在终端上显示。也许git正在执行一些奇怪的仅终端可见的操作? - Delan Azabani
@Delan,如果这是正确的顺序(感谢@Roman澄清),你应该通过strace来查找git正在进行的操作。 - mvds
针对此问题,有三个建议:使用适当的选项强制 git 输出类似 tty 的输出,从源代码中删除 isatty(),或预加载一个使 isatty() 始终返回 1 的库。 - mvds
显示剩余4条评论

1

鉴于名为'a'的FIFO实验,我认为问题在于zenity处理其输入的方式。如果您从键盘键入到zenity会发生什么?(怀疑:它会像您想要的那样读取EOF。)但是,zenity可能使用常规阻塞I/O处理终端输入(tty输入),但对于所有其他设备类型则使用非阻塞I/O。非阻塞I/O对于来自文件的输入很好;但对于来自管道或FIFO等的输入则不太理想。如果它确实使用了非阻塞I/O,则zenity将获得第一行输出,然后退出循环,因为其第二次读取尝试将指示没有其他立即可用的内容。

证明这是否是正在发生的事情(或不是)将很棘手。我将寻找“truss”或“strace”或其他系统调用监视器来跟踪zenity正在做什么。

关于解决方法...如果假设是正确的,那么你需要说服zenity它正在从终端而不是FIFO读取,所以你可能需要设置一个伪终端(或pty);第一个进程将写入pty的主端口,你需要安排zenity从pty的从端口读取。你仍然可以使用FIFO - 尽管它会产生一长串命令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接