2016-04-21 65 views
2

從理論上講,這兩個命令行應該是等價的:爲什麼重定向會在管道出現故障的地方工作?

type tmp.txt | test.exe 
test.exe < tmp.txt 

我有涉及#1的過程,對於很多年了,工作就好了;在去年的某個時候,我們開始用更新版本的Visual Studio編譯程序,並且由於輸入格式錯誤(見下文),它現在失敗了。但#2成功(沒有例外,我們看到預期的輸出)。爲什麼#2會在#1失敗的地方成功?

我已經能夠將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. 
+0

我認爲這個算作現在最小和最完整。爲了驗證,我需要發佈確切的輸入文件,但我沒有看到在這裏附加文件的方法。 –

+0

你可以提供一個程序的源代碼來生成輸入文件,例如[這裏試圖用Python演示相同的問題](https://gist.github.com/zed/dd44ade13d313ceb8ba8e384ba1ff1ac) – jfs

回答

3

這原來是一個known issue

的錯誤是在實際上在較低級別的_read功能,它在標準輸入輸出 庫函數(包括fread和fgets)用於從 文件描述符中讀取數據。

在_read的錯誤如下:如果...

  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庫(雖然這也需要手動 文本模式轉換,因爲OS不會爲 做文本模式轉換)

我們修復了通用CRT下一次更新的這個錯誤。注意 通用CRT是一個操作系統組件,並且是獨立於Visual C++庫服務的 。 Universal CRT的下一次更新 可能與今年夏天的Windows 10週年更新 大約在同一時間範圍內。

相關問題