Tại sao tôi dường như mất dữ liệu khi sử dụng cấu trúc ống bash này?


11

Tôi đang cố gắng kết hợp một vài chương trình như vậy (vui lòng bỏ qua mọi phần bổ sung, đây là một công việc nặng nhọc):

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

Trường hợp nguồn của chương trình lặp lại trông như sau:

#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;
}

Tôi nhận thấy điều này:

  • Khi tôi chỉ sử dụng đường ống để ./repeat, mọi thứ hoạt động như dự định.
  • Khi tôi chỉ sử dụng thay thế quá trình, mọi thứ hoạt động như dự định.
  • Khi tôi đóng gói pv bằng cách sử dụng thay thế quá trình, mọi thứ hoạt động như dự định.
  • Tuy nhiên, khi tôi sử dụng cấu trúc cụ thể, tôi dường như mất dữ liệu (các ký tự riêng lẻ) từ stdin!

Tôi đã thử như sau:

  • Tôi đã cố gắng vô hiệu hóa bộ đệm trên đường ống giữa pv./repeatsử dụng stdbuf -i0 -o0 -e0trên tất cả các quy trình, nhưng điều đó dường như không hoạt động.
  • Tôi đã trao đổi epoll cho cuộc thăm dò, không hoạt động.
  • Khi tôi nhìn vào luồng giữa pv./repeatvới tee stream.csv, điều này có vẻ đúng.
  • Tôi đã từng stracethấy những gì đang diễn ra và tôi thấy rất nhiều lần đọc byte đơn (như mong đợi) và chúng cũng cho thấy dữ liệu đang bị mất.

Tôi tự hỏi chuyện gì đang xảy ra? Hoặc những gì tôi có thể làm để điều tra thêm?

Câu trả lời:


16

Bởi vì nclệnh bên trong <(...)cũng sẽ đọc từ stdin.

Ví dụ đơn giản hơn:

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

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

Đã textđi đâu? Qua mạng mèo.

$ cat /tmp/foo
text

Chương trình của bạn và nccạnh tranh cho cùng một stdin, và ncnhận được một số của nó.


Bạn đúng! Cảm ơn! Bạn có thể đề xuất một cách sạch sẽ để ngắt kết nối stdin trong <(...)? Có cách nào đẹp hơn <( 0<&- ...)không?
Roel Baardman

5
<(... </dev/null). không sử dụng 0<&-: nó sẽ khiến người đầu tiên open(2)trở lại 0là fd mới. Nếu bạn nchỗ trợ nó, bạn cũng có thể sử dụng -dtùy chọn.
mosvy

3

epoll () hoặc poll () trở lại với E / POLLIN sẽ chỉ cho bạn biết rằng một lần đọc () có thể không chặn.

Không phải là bạn sẽ có thể thực hiện nhiều byte đọc () cho đến một dòng mới, như bạn làm.

Tôi nói có thể vì một read () sau epoll () được trả về với E / POLLIN vẫn có thể bị chặn.

Mã của bạn cũng sẽ cố gắng đọc qua EOF và hoàn toàn bỏ qua mọi lỗi đọc ().


Mặc dù điều này không phải là một giải pháp trực tiếp cho vấn đề của tôi, cảm ơn vì đã bình luận. Tôi nhận ra rằng mã này có sai sót và phát hiện EOF có trong phiên bản rút gọn hơn (thông qua việc sử dụng POLLHUP / POLLNVAL). Mặc dù vậy, tôi đấu tranh với việc tìm một cách không có bộ đệm để đọc các dòng từ nhiều mô tả tập tin. repeatChương trình của tôi về cơ bản là xử lý dữ liệu NMEA (dựa trên dòng và không có chỉ số độ dài) từ nhiều nguồn. Vì tôi đang kết hợp dữ liệu từ nhiều nguồn trực tiếp, tôi muốn giải pháp của mình không bị cản trở. Bạn có thể đề xuất một cách hiệu quả hơn để làm điều này?
Roel Baardman

fwiw, thực hiện một cuộc gọi hệ thống (đọc) cho mỗi byte là cách hiệu quả nhất có thể. Kiểm tra EOF có thể được thực hiện bằng cách chỉ kiểm tra giá trị trả về của đọc, không cần POLLHUP (và POLLNVAL sẽ chỉ được trả về khi bạn chuyển nó thành fd không có thật, không phải trên EOF). Nhưng dù sao, hãy theo dõi. Tôi có ý tưởng về một ypeetiện ích đọc từ nhiều fds và trộn chúng vào một fd khác, trong khi vẫn giữ các bản ghi (giữ nguyên các dòng).
pizdelect

Tôi nhận thấy rằng cấu trúc bash này sẽ làm điều đó, nhưng tôi không biết cách kết hợp stdin trong đó: { cmd1 & cmd2 & cmd3; } > fileTệp sẽ chứa những gì bạn mô tả. Tuy nhiên, trong trường hợp của tôi, tôi đang chạy mọi thứ từ tcpserver (3), vì vậy tôi cũng muốn bao gồm stdin (chứa dữ liệu khách hàng). Tôi không chắc làm thế nào để làm điều đó.
Roel Baardman

1
Nó phụ thuộc vào cmd1, cmd2, ... là gì. Nếu họ là mèo hoặc mèo và dữ liệu của bạn được định hướng theo dòng, đầu ra có thể không đúng - bạn sẽ nhận được các dòng bao gồm bắt đầu một dòng được in bởi cmd1 và cuối dòng được in bởi cmd2.
pizdelect
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.