Linux

為什麼我似乎使用這種 bash 管道結構失去了數據?

  • June 10, 2019

我正在嘗試組合一些這樣的程序(請忽略任何額外的包含,這是正在進行的繁重工作):

pv -q -l -L 1  < input.csv | ./repeat <(nc "host" 1234)

重複程序的來源如下所示:

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <string>

inline std::string readline(int fd, const size_t len, const char delim = '\n')
{
   std::string result;
   char c = 0;
   for(size_t i=0; i < len; i++)
   {
       const int read_result = read(fd, &c, sizeof(c));
       if(read_result != sizeof(c))
           break;
       else
       {
           result += c;
           if(c == delim)
               break;
       }
   }
   return result;
}

int main(int argc, char ** argv)
{
   constexpr int max_events = 10;

   const int fd_stdin = fileno(stdin);
   if (fd_stdin < 0)
   {
       std::cerr << "#Failed to setup standard input" << std::endl;
       return -1;
   }


   /* General poll setup */
   int epoll_fd = epoll_create1(0);
   if(epoll_fd == -1) perror("epoll_create1: ");
   {
       struct epoll_event event;
       event.events = EPOLLIN;
       event.data.fd = fd_stdin;
       const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd_stdin, &event);
       if(result == -1) std::cerr << "epoll_ctl add for fd " << fd_stdin << " failed: " << strerror(errno) << std::endl;
   }

   if (argc > 1)
   {
       for (int i = 1; i < argc; i++)
       {
           const char * filename = argv[i];
           const int fd = open(filename, O_RDONLY);
           if (fd < 0)
               std::cerr << "#Error opening file " << filename << ": error #" << errno << ": " << strerror(errno) << std::endl;
           else
           {
               struct epoll_event event;
               event.events = EPOLLIN;
               event.data.fd = fd;
               const int result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
               if(result == -1) std::cerr << "epoll_ctl add for fd " << fd << "(" << filename << ") failed: " << strerror(errno) << std::endl;
               else std::cerr << "Added fd " << fd << " (" << filename << ") to epoll!" << std::endl;
           }
       }
   }

   struct epoll_event events[max_events];
   while(int event_count = epoll_wait(epoll_fd, events, max_events, -1))
   {
       for (int i = 0; i < event_count; i++)
       {
           const std::string line = readline(events[i].data.fd, 512);                      
           if(line.length() > 0)
               std::cout << line << std::endl;
       }
   }
   return 0;
}

我注意到了這一點:

  • 當我只使用管道時./repeat,一切都按預期工作。
  • 當我只使用流程替換時,一切都按預期工作。
  • 當我使用程序替換封裝 pv 時,一切都按預期工作。
  • 但是,當我使用特定結構時,我似乎會從標準輸入中失去數據(單個字元)!

我嘗試了以下方法:

  • 我試圖禁用所有程序之間pv./repeat使用的管道上的緩衝stdbuf -i0 -o0 -e0,但這似乎不起作用。
  • 我已將 epoll 換成 poll,不起作用。
  • 當我查看pv./repeatwith之間的流時tee stream.csv,這看起來是正確的。
  • 我曾經strace看到發生了什麼,我看到很多單字節讀取(如預期的那樣),它們還表明數據失去了。

我想知道發生了什麼事?或者我可以做些什麼來進一步調查?

因為nc裡面的命令<(...)也會從標準輸入讀取。

更簡單的例子:

$ nc -l 9999 >/tmp/foo &
[1] 5659

$ echo text | cat <(nc -N localhost 9999) -
[1]+  Done                    nc -l 9999 > /tmp/foo

去哪兒了text?通過網貓。

$ cat /tmp/foo
text

你的程序和你的程序nc競爭相同的標準輸入,並nc得到一些。

使用 E/POLLIN 返回的 epoll() 或 poll() 只會告訴您單個read()可能不會阻塞。

並不是說您將能夠像您一樣在換行符之前執行很多一個字節的 read()。

我說可能是因為使用 E/POLLIN 返回的 epoll() 之後的 read() 可能仍會阻塞。

您的程式碼還將嘗試讀取過去的 EOF,並完全忽略任何 read() 錯誤。

引用自:https://unix.stackexchange.com/questions/523929