git stderr 输出无法通过管道传输

发布于 2024-09-30 11:22:03 字数 2478 浏览 17 评论 0原文

我正在为 git:// 与 bash 和 zenity 的链接编写一个图形 URI 处理程序,并且使用 zenity 的“文本信息”对话框来显示 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 的文本信息对话框逐步显示所有输出。

git 在 stderr 上输出进度消息(“初始化”消息以外的任何内容),但是当我尝试将 stderr 通过管道传输到文件或与 stdout 合并时,消息就会消失。

修复尝试 1

我尝试用 C、bread 和 bwrite 编写 cat 函数的两个阻塞版本,如下所示:

#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'ing 并检查 git 的源代码之后,我意识到 git 在 stderr 上输出其所有进度信息(“初始化”消息之后的任何内容),并且事实上这是第一行和我的假设是因为 cat 在 EOF 提前退出是一个巧合/误导的假设(git 在程序结束之前不会 EOF)。

情况似乎变得简单了很多,因为我不必对原始代码(在问题开始时)进行任何更改,并且它应该可以工作。然而,神秘的是,重定向时 stderr 输出会“消失”——而这只是 git 中发生的事情。

测试用例?试试这个,看看你是否在文件中看到任何东西(你不会):

git clone git://anongit.freedesktop.org/xorg/proto/dri2proto 2> hurr

这违背了我所知道的关于 stderr 和重定向的一切;我什至编写了一个在 stderr 和 stdout 上输出的小 C 程序,以向自己证明重定向对 git 不起作用。

修复尝试 4

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

成功!

非常感谢您的帮助。这是固定行:

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

I'm writing a graphical URI handler for git:// links with bash and zenity, and I'm using a zenity 'text-info' dialog to show git's clone output while it's running, using FIFO piping. The script is about 90 lines long, so I won't bother posting it here, but here's the most important lines:

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

I'm using FIFO instead of a direct pipe to allow them to run asynchronously and allow for killing git if the zenity window is closed.

Problem is, the only line that appears from git's output is the first:

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

The other lines with counting objects, etc. don't show or are shown on the terminal.

Current reason

The current consensus as to why this isn't working seems to be that cat is non-blocking and quits after the first line, only passing that to zenity and not the rest. My aim is to force blocking for the read, and have zenity's text info dialog show all output progressively.

git outputs progress messages (anything other than the "Initialized" message) on stderr, but the moment I try to pipe stderr to a file or to merge with stdout, the messages disappear.

Fix attempt 1

I've tried to write two blocking versions of cat's functions in C, bread and bwrite, like this:

#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);
    }
}

They work nicely because they block and don't quit on EOF, but it hasn't quite solved the problem yet. At the moment, using one, the other, or both, works in theory, but in practice, zenity shows nothing at all now.

Fix attempt 2

@mvds suggested that using a regular file, in combination with tail -f rather than cat, may do this. Surprised at such a simple solution (thanks!) I tried it but unfortunately, only the first line showed up in zenity and nothing else.

Fix attempt 3

After doing some strace'ing and inspecting git's source code, I realise that git outputs all its progress information (anything past the "Initialized" message) on stderr, and the fact that this is the first line and my assumption that it's because of cat quitting early on EOF was a coincidence/misguided assumption (git doesn't EOF until the program ends).

The situation seemed to become a lot simpler, as I shouldn't have to change anything from the original code (at the start of the question) and it should work. Mysteriously, however, the stderr output 'vanishes' when redirected - and this is only something that happens in git.

Test case? Try this, and see if you see anything in the file (you won't):

git clone git://anongit.freedesktop.org/xorg/proto/dri2proto 2> hurr

This goes against everything I know about stderr and redirection; I've even written a little C program that outputs on stderr and stdout to prove to myself that redirection just doesn't work for git.

Fix attempt 4

In line with Jakub Narębski's answer, as well as replies to emails I sent to the git mailing list, --progress is the option I need. Note that this option only works after the command, and not before clone.

Success!

Thank you very much for all your help. This is the fixed line:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

后来的我们 2024-10-07 11:22:03

我认为当输出不是终端(tty)时,至少有一些进度报告会被静音。我不确定它是否适用于您的情况,但尝试将 --progress 选项传递给“git clone”(即使用 git clone --progress <存储库>)。

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

I think that at least some of progress reports gets silenced when output is not a terminal (tty). I'm not sure if it applies to your case, but try to pass --progress option to 'git clone' (i.e. use git clone --progress <repository>).

Though I don't know if it is what you wanted to have.

一方面,输出重定向是从右到左解析的,因此

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 可能会揭示您正在启动的进程的内部工作原理。对于没有经验的人来说,与普通管道相比,命名管道会让事情变得更糟。

For one thing, the output redirection is parsed right-to-left, so

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

is not equal to

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

The latter will redirect stderr to stdout, and then stdout (including stderr) to the file. The former will redirect stdout to the file, and then show stderr on stdout.

As for piping to zenity (which I don't know), I think you may be making things overly complicated with the named pipe. Using strace may shed some light on the inner workings of the processes you're firing up. For the inexperienced, named pipes make things worse compared to normal pipes.

(り薆情海 2024-10-07 11:22:03

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

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

至于解决方法……如果假设是正确的,那么您需要说服 zenity 它是从终端而不是 FIFO 读取数据,因此您可能需要安装一个伪 tty(或 pty);第一个进程将写入 pty 的主端,并且您将安排 zenity 从 pty 的从端读取。您可能仍然使用 FIFO - 尽管它会产生很长的命令链。

Given the experiment with the FIFO called 'a', I think the problem lies in the way zenity processes its input. What happens if you type into zenity from the keyboard? (Suspicion: it behaves as you'd want, reading to EOF.) However, it could be that zenity handles terminal input (tty input) using regular blocking I/O but uses non-blocking I/O for all other device types. Non-blocking I/O is fine for input from files; it is less desirable for input from pipes or FIFOs, etc. If it did use non-blocking I/O, zenity would get the first line of output, and then exit the loop thinking it was done because its second read attempt would indicate that there was nothing else immediately available.

Demonstrating that this is what is happening (or not) will be tricky. I would be looking to 'truss' or 'strace' or other system call monitor to track what zenity is doing.

As to workarounds...if the hypothesis is correct, then you'll need to persuade zenity that it is reading from a terminal and not a FIFO, so you'll probably need to rig up a pseudo-tty (or pty); the first process would write to the master end of the pty and you'd arrange for zenity to read from the slave end of the pty. You might still use the FIFO too - though it makes a long chain of command.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文