Perché sembra che perda i dati usando questa costruzione del tubo bash?


11

Sto cercando di combinare alcuni programmi in questo modo (per favore ignora eventuali inclusioni extra, questo è un intenso lavoro in corso):

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

Dove l'origine del programma di ripetizione appare come segue:

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

Ho notato questo:

  • Quando uso semplicemente la pipa per ./repeat, tutto funziona come previsto.
  • Quando uso solo la sostituzione del processo, tutto funziona come previsto.
  • Quando incapsulo il pv usando la sostituzione del processo, tutto funziona come previsto.
  • Tuttavia, quando uso la costruzione specifica, mi sembra di perdere dati (singoli personaggi) da stdin!

Ho provato quanto segue:

  • Ho provato a disabilitare il buffering sul pipe tra pve l' ./repeatutilizzo stdbuf -i0 -o0 -e0su tutti i processi, ma questo non sembra funzionare.
  • Ho scambiato epoll con sondaggio, non funziona.
  • Quando guardo il flusso tra pve ./repeatcon tee stream.csv, questo sembra corretto.
  • Ero abituato stracea vedere cosa stava succedendo e vedo molte letture a byte singolo (come previsto) e mostrano anche che mancano i dati.

Mi chiedo cosa stia succedendo? O cosa posso fare per indagare ulteriormente?

Risposte:


16

Perché il nccomando dentro <(...)leggerà anche da stdin.

Esempio più semplice:

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

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

Dove è textandato? Attraverso il netcat.

$ cat /tmp/foo
text

Il tuo programma e nccompeti per lo stesso stdin, e ne ncottiene un po '.


Hai ragione! Grazie! Puoi suggerire un modo pulito per disconnettere stdin in <(...)? C'è un modo migliore di <( 0<&- ...)?
Roel Baardman,

5
<(... </dev/null). non usare 0<&-: farà open(2)tornare il primo 0come nuovo fd. Se nclo supporta, è anche possibile utilizzare l' -dopzione.
mosvy,

3

epoll () o poll () che ritornano con E / POLLIN ti diranno solo che un singolo read () potrebbe non bloccarsi .

Non che sarai in grado di fare molti byte (read) fino a una nuova riga, come fai tu.

Dico may perché un read () dopo epoll () restituito con E / POLLIN potrebbe ancora bloccarsi.

Il codice tenterà anche di leggere EOF passato e ignora completamente qualsiasi errore read ().


Nonostante ciò non sia una soluzione diretta al mio problema, grazie per aver commentato. Mi rendo conto che questo codice presenta dei difetti e che il rilevamento EOF è presente in una versione meno ridotta (tramite l'uso di POLLHUP / POLLNVAL). Faccio fatica a trovare un modo senza buffer per leggere righe da più descrittori di file. Il mio repeatprogramma sta essenzialmente elaborando dati NMEA (basati su linea e senza indicatori di lunghezza) da più fonti. Dal momento che sto combinando dati provenienti da più origini live, vorrei che la mia soluzione fosse senza buffer. Puoi suggerire un modo più efficiente per farlo?
Roel Baardman,

prima di tutto, effettuare una chiamata di sistema (lettura) per ogni byte è il modo meno efficiente possibile. Il controllo EOF può essere eseguito semplicemente controllando il valore restituito di read, non è necessario POLLHUP (e POLLNVAL verrà restituito solo quando lo si passa a un falso fd, non su EOF). Ma comunque, rimanete sintonizzati. Ho l'idea di ypeeun'utilità che legge da più fd e li mescola in un altro fd, preservando i record (mantenendo intatte le linee).
seleziona il

Ho notato che questa costruzione bash dovrebbe farlo, ma non so come combinare stdin in essa: il { cmd1 & cmd2 & cmd3; } > filefile conterrà ciò che descrivi. Tuttavia, nel mio caso sto eseguendo tutto da tcpserver (3), quindi voglio includere anche stdin (che contiene i dati del client). Non sono sicuro di come farlo.
Roel Baardman,

1
Dipende da cosa sono cmd1, cmd2, ... Se sono nc o cat e i tuoi dati sono orientati alla linea, l'output potrebbe non essere corretto: otterrai linee costituite dall'inizio di una riga stampata da cmd1 e dalla fine di una riga stampata da cmd2.
seleziona il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.