Чому я, здається, втрачаю дані, використовуючи цю конструкцію баш-труби?


11

Я намагаюся поєднати кілька таких програм (будь-ласка, ігноруйте будь-які додаткові включення, це важке незавершене виробництво):

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, використовуючи процес підстановки, все працює за призначенням.
  • Однак, коли я використовую конкретну конструкцію, я, здається, втрачаю дані (окремі символи) від stdin!

Я спробував таке:

  • Я намагався відключити буферизацію на трубі між pvі./repeat використовувати stdbuf -i0 -o0 -e0для всіх процесів, але це, здається, не працює.
  • Я поміняв еполю для опитування, не працює.
  • Коли я дивлюся на потік між pvі ./repeatзtee stream.csv , це виглядає правильно.
  • Я straceбачив, що відбувається, і бачу багато однобайтових читання (як очікувалося), і вони також показують, що дані пропадають.

Цікаво, що відбувається? Або що я можу зробити для подальшого дослідження?

Відповіді:


16

Тому що ncкоманда всередині<(...) також читатиметься з stdin.

Простіший приклад:

$ 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змагається за той самий stdin, і ncотримує частину цього.


Ти маєш рацію! Спасибі! Чи можете ви запропонувати чистий спосіб відключити stdin у <(...)? Чи є приємніший спосіб, ніж <( 0<&- ...)?
Роель Баардман

5
<(... </dev/null). не використовуйте 0<&-: це призведе до того, що перший open(2)повернеться 0як новий fd. Якщо ваші ncпідтримують це, ви також можете скористатися -dопцією.
mosvy

3

epoll () або опитування (), що повертається з E / POLLIN, скаже лише вам, що одне зчитування () може не блокувати.

Не те, що ви зможете зробити багато прочитаних () байтів до нового рядка, як і ви.

Я кажу, може тому , що читання () після Epoll () повернувся з E / POLLIN ще може блокувати.

Ваш код також намагатиметься прочитати минулий EOF і повністю ігнорує будь-які помилки read ().


Незважаючи на те, що це не є прямим вирішенням моєї проблеми, дякую за коментар. Я усвідомлюю, що у цього коду є недоліки, і виявлення EOF є у менш зведеній версії (через використання POLLHUP / POLLNVAL). Я все ж борюся з пошуком розблокованого способу для читання рядків із декількох дескрипторів файлів. Моя repeatпрограма по суті обробляє дані NMEA (на основі рядків і без індикаторів довжини) з декількох джерел. Оскільки я поєдную дані з декількох прямих джерел, я б хотів, щоб моє рішення було нерозкрите. Чи можете ви запропонувати більш ефективний спосіб зробити це?
Роель Баардман

fwiw, здійснення системного виклику (читання) для кожного байту - це найменш ефективний спосіб. Перевірку EOF можна здійснити, просто перевіривши повернене значення прочитаного, немає необхідності в POLLHUP (і POLLNVAL буде повернуто лише тоді, коли ви передасте його фальшивий fd, а не EOF). Але в будь-якому разі, будьте в курсі. У мене є ідея ypeeутиліти, яка читає з декількох файлів і змішує їх в інший fd, зберігаючи записи (зберігаючи рядки неушкодженими).
pizdelect

Я помітив, що ця баш-конструкція повинна це робити, але я не знаю, як поєднувати stdin в ньому: { cmd1 & cmd2 & cmd3; } > fileФайл буде містити те, що ви описуєте. Однак у моєму випадку я запускаю все від tcpserver (3), тому хочу включити stdin (який містить дані клієнта). Я не впевнений, як це зробити.
Роель Баардман

1
Це залежить від того, що таке cmd1, cmd2, ... Якщо вони nc або cat і ваші дані орієнтовані на рядки, результат може бути неправильним - ви отримаєте рядки, що складаються з початку рядка, надрукованого cmd1, і кінця рядка, надрукованого cmd2.
pizdelect
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.