Dlaczego wydaje mi się, że tracę dane przy użyciu tej konstrukcji rurki bash?


11

Próbuję połączyć kilka takich programów (proszę zignorować wszelkie dodatkowe dołączenia, jest to ciężka praca w toku):

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

Gdzie źródło programu powtarzającego wygląda następująco:

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

Zauważyłem to:

  • Kiedy używam potoku do ./repeat, wszystko działa zgodnie z przeznaczeniem.
  • Kiedy używam po prostu podstawienia procesu, wszystko działa zgodnie z przeznaczeniem.
  • Kiedy enkapsuluję pv za pomocą podstawienia procesu, wszystko działa zgodnie z przeznaczeniem.
  • Jednak gdy używam określonej konstrukcji, wydaje mi się, że tracę dane (pojedyncze znaki) ze standardowego wejścia!

Próbowałem następujące:

  • Próbowałem wyłączyć buforowanie na potoku między wszystkimi procesami pvi ./repeatkorzystanie stdbuf -i0 -o0 -e0z nich, ale wydaje się, że to nie działa.
  • Zamieniłem epoll na ankietę, nie działa.
  • Kiedy patrzę na strumień między pvi ./repeatz tee stream.csv, wygląda to poprawnie.
  • Widziałem, straceco się dzieje i widzę wiele odczytów jednobajtowych (zgodnie z oczekiwaniami), a także pokazują, że brakuje danych.

Zastanawiam się, co się dzieje? Lub co mogę zrobić, aby przeprowadzić dalsze dochodzenie?

Odpowiedzi:


16

Ponieważ ncpolecenie wewnątrz <(...)będzie również czytać ze standardowego wejścia.

Prostszy przykład:

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

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

Gdzie textposzedł? Przez Netcat.

$ cat /tmp/foo
text

Twój program i ncwalcz o ten sam standard, i nczdobędziesz go.


Masz rację! Dzięki! Czy możesz zasugerować czysty sposób na odłączenie stdin w <(...)? Czy jest lepszy sposób <( 0<&- ...)?
Roel Baardman,

5
<(... </dev/null). nie używaj 0<&-: spowoduje to, że pierwszy open(2)zwróci 0jako nowy fd. Jeśli ncto obsługuje, możesz również skorzystać z tej -dopcji.
mosvy

3

Zwrócenie epoll () lub poll () z E / POLLIN powie ci tylko, że pojedynczy read () może nie zostać zablokowany.

Nie znaczy to, że będziesz w stanie wykonać wiele bajtów read () aż do nowego wiersza, tak jak robisz.

Mówię, że może, ponieważ funkcja read () po epoll () zwrócona z E / POLLIN może nadal blokować.

Twój kod spróbuje również odczytać wcześniejsze EOF i całkowicie ignoruje wszelkie błędy read ().


Mimo, że nie jest to bezpośrednie rozwiązanie mojego problemu, dziękuję za komentarz. Zdaję sobie sprawę, że ten kod ma wady, a wykrywanie EOF występuje w mniej uproszczonej wersji (poprzez użycie POLLHUP / POLLNVAL). Trudno mi jednak znaleźć niebuforowany sposób czytania linii z wielu deskryptorów plików. Mój repeatprogram zasadniczo przetwarza dane NMEA (oparte na linii i bez wskaźników długości) z wielu źródeł. Ponieważ łączę dane z wielu źródeł na żywo, chciałbym, aby moje rozwiązanie nie było buforowane. Czy możesz zasugerować bardziej skuteczny sposób to zrobić?
Roel Baardman,

fwiw, wykonywanie wywołania systemowego (odczytu) dla każdego bajtu jest najmniej wydajnym sposobem. Sprawdzanie EOF może być wykonane po prostu przez sprawdzenie zwracanej wartości read, bez potrzeby POLLHUP (a POLLNVAL zostanie zwrócony tylko wtedy, gdy przekażesz mu fałszywy fd, nie na EOF). Ale i tak bądź na bieżąco. Mam pomysł na ypeenarzędzie, które odczytuje z wielu fds i miksuje je w innym fd, zachowując jednocześnie rekordy (zachowując nienaruszone linie).
pizdelect,

Zauważyłem, że ta konstrukcja bash powinna to zrobić, ale nie wiem, jak połączyć w niej stdin: { cmd1 & cmd2 & cmd3; } > fileplik będzie zawierał to, co opisujesz. Jednak w moim przypadku uruchamiam wszystko z tcpserver (3), więc chcę również dołączyć stdin (który zawiera dane klienta). Nie jestem pewien jak to zrobić.
Roel Baardman

1
To zależy od tego, jakie są cmd1, cmd2, ... Jeśli są to nc lub cat, a dane są zorientowane liniowo, dane wyjściowe mogą być zniekształcone - otrzymasz linie składające się z początku linii wydrukowanej przez cmd1 i końca linii wydrukowanej przez cmd2.
pizdelect
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.