无法从通过管道 C++ 重定向的程序中获得正确的输出(Windows)

Posted

技术标签:

【中文标题】无法从通过管道 C++ 重定向的程序中获得正确的输出(Windows)【英文标题】:Cannot get correct output from program redirected via pipe C++ (Windows) 【发布时间】:2017-02-22 00:10:16 【问题描述】:

我想实现一个结果,让程序的输出拆分到双端队列结构。

描述问题:我正在处理使用 CreateProcess 创建的程序的输出重定向。我需要读取程序的输出并逐行处理。程序本身以方便的形式提供输出,但我通过未命名管道接收的输出传输速度较慢(频率较小),它们出现在部分中,并且最后一行在某处被切成两半。来自管道的流的下一部分将匹配并完成该行,但它会给程序的构建带来一些问题。

程序的 cout 和通过管道重定向的输出之间存在这种差异的原因是什么?

编辑:根据提议 user4581301 我尝试使用 stringstream 和 getline,但似乎这些行仍然被切成两半,即使程序中的直接 cout 没有重定向到管道没有这个问题。这会导致问题,即行在队列的不同元素中被拆分(请查看下面的代码)。

Sample from the console

ReadFromPipe 方法在循环中运行。

void ProcessManagement::ReadFromPipe(void)

// Read output from the child process's pipe for STDOUT


    DWORD dwRead, dwWritten;
    char buffer[BUFSIZE];
    BOOL bSuccess = FALSE;
    std::deque<std::deque<std::string>> elems;

while (ReadFile(g_hChildStd_OUT_Rd, buffer, sizeof(buffer)-1, &dwRead, NULL) != FALSE)

    /* add terminating zero */
    buffer[dwRead] = '\0';
    std::stringstream streamBuffer;
    streamBuffer << buffer;
    BufferToQueue(streamBuffer, ' ', elems);

    // Print deque
    for (std::deque <std::string> a : elems)
    
        for (std::string b : a)
            std::cout << b;
        std::cout << std::endl;
    

    

还有方法BufferToQueue。

void ProcessManagement::BufferToQueue(std::stringstream &streamBuffer, char delim, std::deque<std::deque<std::string>> &elems) 
    std::string line;
    std::string word;
    std::deque<string> row;

 // Splitting the stringstream into queue of queue (does not work properly)
    while (std::getline(streamBuffer, line))
    
        std::istringstream iss(line);
        while (std::getline(iss, word, delim))
            row.push_back(word);
    elems.push_back(row);
    row.clear();
    

【问题讨论】:

闻起来像是忘记冲水了。 离题:您是否考虑过将管道输出写入std::stringstream,然后在流上使用std::getline 读取? 根据建议编辑的问题。我没有尝试更多的刷新,因为在屏幕上发送文本并不是应用程序正常工作所必需的,它只是开始检查如何编程行为的一种方式。我担心的是,字符串在用 getline 拆分后没有正确放入队列。 【参考方案1】:

扩展@Captain Obvlious 关于刷新的评论:

您面临的问题是因为 WriteToPipe 函数不会在行尾刷新。如果前一个 ReadFromPipe 调用没有 newline 作为最后一个字符,您可以通过确保附加到前一个字符串在阅读器中解决此问题。

修改功能:

bool ProcessManagement::BufferToQueue(std::stringstream &streamBuffer, char     delim, std::deque<std::deque<std::string>> &elems)

    std::string line;
    std::string word;
    std::deque<string> row;

    bool is_unflushed_line = streamBuffer.str().back() != '\n';

    // Splitting the stringstream into queue of queue (does not work properly)
    while (std::getline(streamBuffer, line))
    
        std::istringstream iss(line);
        while (std::getline(iss, word, delim)) 
            row.push_back(word);
        
        elems.push_back(row);
        row.clear();
    
    return is_unflushed_line;


void ProcessManagement::ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT

    DWORD dwRead, dwWritten;
    char buffer[BUFSIZE];
    BOOL bSuccess = FALSE;
    std::deque<std::deque<std::string>> elems;

    while (ReadFile(g_hChildStd_OUT_Rd, buffer, sizeof(buffer)-1, &dwRead, NULL) != FALSE)
    
        /* add terminating zero */
        buffer[dwRead] = '\0';
        std::stringstream streamBuffer;
        streamBuffer << buffer;
        bool is_unflushed_line = BufferToQueue(streamBuffer, ' ', elems);

        for(auto idx = 0; idx != elems.size(); ++idx)
        
            for (std::string const& b : elems[idx])
                std::cout << b;
            if(idx == elems.size() - 1 && is_unflushed_line)
                break;// don't print a newline if input did not end with a newline
            std::cout << std::endl;
        
    

【讨论】:

感谢您的回复。它为我指明了方向,问题是,未正确复制的字符串不仅出现在字符串流的末尾,可能是拆分功能的问题吗?我过去也使用它,但没有任何问题。 我不知道字符串流是否有可能从管道中收到任何“垃圾”?似乎拆分不仅在字符串流的末尾不能正常工作(用“if (idx == elems.size() - 1) std::cout 我所做的是我正在检查创建的双端队列中的第二个元素,并且在某些情况下放置了不正确的字符串(即,该行被拆分到其他地方并且行尾是推入新行)。冲洗还有问题吗?因为正如我提到的,它不仅发生在 stringstream 的末尾。【参考方案2】:

@indeterminately sequenced 的答案是正确的,谢谢你的帮助。问题是,管道复制到的缓冲区太小,它正在将其分配给单独的缓冲区。

在@indeterminately 的帮助下将输出推送到队列结构的完整解决方案,也许它会对某人有所帮助。唯一的问题是打开的程序永远不会关闭,必须在某个地方使用 TerminateProcess 函数。

void ProcessManagement::CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.

    SECURITY_ATTRIBUTES saAttr =  sizeof(SECURITY_ATTRIBUTES) ;

    saAttr.bInheritHandle = TRUE;   //Pipe handles are inherited by child process.
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe to get results from child's stdout.
    if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
        return;

    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdOutput = hPipeWrite;
    si.hStdError = hPipeWrite;
    si.wShowWindow = SW_HIDE;       // Prevents cmd window from flashing. Requires STARTF_USESHOWWINDOW in dwFlags.

    std::string command_temp = (" -i \"LAN 3\"");
    LPSTR st = const_cast<char *>(command_temp.c_str());

    BOOL fSuccess = CreateProcessA(program_path.c_str(), st, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    if (!fSuccess)
    
        CloseHandle(hPipeWrite);
        CloseHandle(hPipeRead);
        return ;
    

    /* Needs to be used somewhere
    TerminateProcess(pi.hProcess,exitCode);
    CloseHandle(hPipeWrite);
    CloseHandle(hPipeRead);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);*/


void ProcessManagement::ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT

    std::deque<std::deque<std::string>> elems;

    // Give some timeslice (50ms), so we won't waste 100% cpu.
    bProcessEnded = WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0;

    // Even if process exited - we continue reading, if there is some data available over pipe.
    for (;;)
    
        char buf[8192];
        DWORD dwRead = 0;
        DWORD dwAvail = 0;

        if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
            break;

        if (!dwAvail) // no data available, return
            break;
        if (!::ReadFile(hPipeRead, buf, min(sizeof(buf)-1, dwAvail), &dwRead, NULL) || !dwRead)
            // error, the child process might ended
            break;
        buf[dwRead] = '\0';
        std::stringstream streamBuffer;
        streamBuffer << unflushed_line << buf; // add to buffer also last unflashed line
        unflushed_line = BufferToQueue(streamBuffer, ' ', elems);

        for (auto idx = 0; idx != elems.size(); ++idx)
        
            for (std::string const& b : elems[idx])
                std::cout << b;

            std::cout << std::endl;
        

    


std::string ProcessManagement::BufferToQueue(std::stringstream &streamBuffer, char delim, std::deque<std::deque<std::string>> &elems) 
    std::string line;
    std::string word;
    std::deque<string> row;

    bool is_unflushed_line = streamBuffer.str().back() != '\n';

    // Splitting the stringstream into queue of queue (does not work properly)
    while (std::getline(streamBuffer, line, '\n'))
    
        std::istringstream iss(line);
        while (std::getline(iss, word, delim)) 
            row.push_back(word);
        
        elems.push_back(row);
        row.clear();
    

    if (is_unflushed_line)
    
        elems.pop_back(); // pop not fully flushed line
    
    else line.clear(); // if the line was fully flushed return empty string

    return line; // to add to buffer for next push to queue if the last was not flushed at the end


【讨论】:

无论如何,是否知道如何使这种冲洗更快一点,这样它就不会等到它满了(因为我猜这就是正在发生的事情)?在 CreatePipe 函数中更改管道大小不会产生任何变化,所以也许我的推理是错误的。函数FlushFileBuffers 在这里也无济于事。

以上是关于无法从通过管道 C++ 重定向的程序中获得正确的输出(Windows)的主要内容,如果未能解决你的问题,请参考以下文章

Appium 错误:无法创建新会话。 (原始错误:没有从 Chromedriver 获得会话重定向)

在管道中强制标准输出的行缓冲

将子进程的 stdout 和 stderr 重定向到两个命名管道(然后从它们读回)

3.管道符重定向与环境变量

linux 从入门到跑路–重定向 管道

重定向与管道