windows - 为什么在管道发生故障时重定向会起作用?

标签 windows command-line

理论上,这两个命令行应该是等效的:

1

type tmp.txt | test.exe

2

test.exe < tmp.txt

我有一个涉及 #1 的流程,多年来一直运行良好;去年的某个时候,我们开始使用较新版本的 Visual Studio 编译该程序,但现在由于输入格式错误而失败(见下文)。但 #2 成功了(也不异常(exception),我们看到了预期的输出)。为什么 #2 会成功,而 #1 会失败?

我已经能够将 test.exe 缩减为下面的程序。我们的输入文件每行只有一个制表符,并且统一使用 CR/LF 行结尾。所以这个程序永远不应该写入 stderr:

#include <iostream>
#include <string>

int __cdecl main(int argc, char** argv)
{
    std::istream* pIs = &std::cin;
    std::string line;

    int lines = 0;
    while (!(pIs->eof()))
    {
        if (!std::getline(*pIs, line))
        {
            break;
        }

        const char* pLine = line.c_str();
        int tabs = 0;
        while (pLine)
        {
            pLine = strchr(pLine, '\t');
            if (pLine)
            {
                // move past the tab
                pLine++;
                tabs++;
            }
        }

        if (tabs > 1)
        {
            std::cerr << "We lost a linebreak after " << lines << " good lines.\n";
            lines = -1;
        }

        lines++;
    }

    return 0;
}

当通过 #1 运行时,我得到以下输出,每次都有相同的数字(在每种情况下,这是因为 getline 返回了两个串联的行,没有中间换行符);当通过 #2 运行时,(正确地)没有输出:

We lost a linebreak after 8977 good lines.
We lost a linebreak after 1468 good lines.
We lost a linebreak after 20985 good lines.
We lost a linebreak after 6982 good lines.
We lost a linebreak after 1150 good lines.
We lost a linebreak after 276 good lines.
We lost a linebreak after 12076 good lines.
We lost a linebreak after 2072 good lines.
We lost a linebreak after 4576 good lines.
We lost a linebreak after 401 good lines.
We lost a linebreak after 6428 good lines.
We lost a linebreak after 7228 good lines.
We lost a linebreak after 931 good lines.
We lost a linebreak after 1240 good lines.
We lost a linebreak after 2432 good lines.
We lost a linebreak after 553 good lines.
We lost a linebreak after 6550 good lines.
We lost a linebreak after 1591 good lines.
We lost a linebreak after 55 good lines.
We lost a linebreak after 2428 good lines.
We lost a linebreak after 1475 good lines.
We lost a linebreak after 3866 good lines.
We lost a linebreak after 3000 good lines.

最佳答案

结果是 known issue :

The bug is in fact in the lower-level _read function, which the stdio library functions (including both fread and fgets) use to read from a file descriptor.

The bug in _read is as follows: If…

  1. you are reading from a text mode pipe,
  2. you call _read to read N bytes,
  3. _read successfully reads N bytes, and
  4. the last byte read is a carriage return (CR) character,

then the _read function will complete the read successfully but will return N-1 instead of N. The CR or LF character at the end of the result buffer is not counted in the return value.

In the specific issue reported in this bug, fread calls _read to fill the stream buffer. _read reports that it filled N-1 bytes of the buffer and the final CR or LF character is lost.

The bug is fundamentally timing-sensitive because whether _read can successfully read N bytes from the pipe depends on how much data has been written to the pipe. Changing the buffer size or changing when the buffer is flushed may reduce the likelihood of the problem, but it won’t necessarily work around the problem in 100% of cases.

There are several possible workarounds:

  1. use a binary pipe and do text mode CRLF => LF translation manually on the reader side. This is not particularly difficult to do (scan the buffer for CRLF pairs; replace them with a single LF).
  2. call ReadFile with _osfhnd(fh), bypassing the CRT’s I/O library on the reader side entirely (though this would also require manual text mode translation, since the OS won’t do text mode translation for you)

We have fixed this bug for the next update to the Universal CRT. Note that the Universal CRT is an operating system component and is serviced independently from the Visual C++ libraries. The next update to the Universal CRT will probably be around the same timeframe as the Windows 10 Anniversary Update this summer.

关于windows - 为什么在管道发生故障时重定向会起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36781891/

相关文章:

java - 在Windows下的java中创建目录的硬链接(hard link)

c - 如何将命令行参数作为字符串参数的一部分传递

windows - 链接 : . a、.lib 和 .def 文件

Java:如何检查程序是否已正确启动?

windows - 当稀疏数组变大时,如何解决元素不再显示的问题?

windows - R Markdown v2 到 pdf。绘图中出现非拉丁字符时出现转换错误

perl - 在 Perl 脚本中同时使用 ARGV 和 CGI

c# - 如何在输入时读取 C# 控制台应用程序的整个命令行?

command-line - MinGW make 命令的奇怪问题(使用 muParser)?

c - 从 C 中的命令行管道传输文件