【问题标题】:Why would redirection work where piping fails?为什么重定向会在管道失败的地方起作用?
【发布时间】:2016-04-21 23:00:32
【问题描述】:

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

1

type tmp.txt | test.exe

2

test.exe < tmp.txt

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

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

#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.

【问题讨论】:

标签: windows command-line


【解决方案1】:

原来是known issue

错误实际上是在较低级别的 _read 函数中,即 stdio 库函数(包括 fread 和 fgets)用于从 文件描述符。

_read中的bug如下:如果……

  1. 您正在从文本模式管道读取,
  2. 你调用 _read 来读取 N 个字节,
  3. _read 成功读取 N 个字节,并且
  4. 读取的最后一个字节是回车 (CR) 字符,

那么 _read 函数将成功完成读取,但会 返回 N-1 而不是 N。末尾的 CR 或 LF 字符 结果缓冲区不计入返回值。

在这个bug报告的具体问题中,fread调用_read来填充 流缓冲区。 _read 报告它填充了 N-1 个字节 缓冲区和最后的 CR 或 LF 字符丢失。

这个错误基本上是时间敏感的,因为 _read 是否可以 从管道中成功读取 N 个字节取决于有多少数据 已写入管道。更改缓冲区大小或更改时间 缓冲区被刷新可能会降低问题的可能性,但它 不一定能在 100% 的情况下解决问题。

有几种可能的解决方法:

  1. 使用二进制管道并在阅读器端手动执行文本模式 CRLF => LF 翻译。这并不是特别难做到 (扫描缓冲区中的 CRLF 对;用单个 LF 替换它们)。
  2. 使用 _osfhnd(fh) 调用 ReadFile,在阅读器端完全绕过 CRT 的 I/O 库(尽管这也需要手动 文本模式翻译,因为操作系统不会为 你)

我们已为通用 CRT 的下一次更新修复了此错误。笔记 通用 CRT 是一个操作系统组件,并且是 独立于 Visual C++ 库提供服务。下次更新 到 Universal CRT 的时间可能与 今年夏天 Windows 10 周年更新。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多