Warum scheine ich mit dieser Bash-Pipe-Konstruktion Daten zu verlieren?


11

Ich versuche, ein paar Programme wie dieses zu kombinieren (bitte ignorieren Sie alle zusätzlichen Includes, dies ist eine schwere Arbeit in Arbeit):

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

Wo die Quelle des Wiederholungsprogramms wie folgt aussieht:

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

Mir ist Folgendes aufgefallen:

  • Wenn ich nur das Rohr benutze ./repeat, funktioniert alles wie vorgesehen.
  • Wenn ich nur die Prozessersetzung verwende, funktioniert alles wie vorgesehen.
  • Wenn ich pv mithilfe der Prozessersetzung einkapsele, funktioniert alles wie beabsichtigt.
  • Wenn ich jedoch die spezifische Konstruktion verwende, scheine ich Daten (einzelne Zeichen) von stdin zu verlieren!

Ich habe folgendes versucht:

  • Ich habe versucht, die Pufferung in der Pipe zwischen pvund ./repeatbei stdbuf -i0 -o0 -e0allen Prozessen zu deaktivieren , aber das scheint nicht zu funktionieren.
  • Ich habe Epoll gegen Umfrage getauscht, funktioniert nicht.
  • Wenn ich mir den Stream zwischen pvund ./repeatmit tee stream.csvansehe, sieht das richtig aus.
  • Früher stracehabe ich gesehen, was los war, und ich sehe viele Einzelbyte-Lesevorgänge (wie erwartet), und sie zeigen auch, dass Daten fehlen.

Ich frage mich, was los ist? Oder was kann ich tun, um weitere Untersuchungen durchzuführen?

Antworten:


16

Weil der ncBefehl im Inneren <(...)auch von stdin liest.

Einfacheres Beispiel:

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

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

Wo ist das texthingegangen? Durch die Netzkatze.

$ cat /tmp/foo
text

Ihr Programm und nckonkurrieren um den gleichen Standard und bekommen ncetwas davon.


Du hast recht! Vielen Dank! Können Sie einen sauberen Weg vorschlagen, um stdin in der zu trennen <(...)? Gibt es einen schöneren Weg als <( 0<&- ...)?
Roel Baardman

5
<(... </dev/null). Verwenden Sie nicht 0<&-: es bewirkt , dass die erste open(2)Rückkehr 0als neuen fd. Wenn Sie dies ncunterstützen, können Sie die -dOption auch verwenden .
Mosvy

3

epoll () oder poll (), die mit E / POLLIN zurückkehren, sagen Ihnen nur, dass ein einzelnes read () möglicherweise nicht blockiert.

Nicht, dass Sie in der Lage wären, viele Ein-Byte-Lesevorgänge () bis zu einer neuen Zeile auszuführen, wie Sie es tun.

Ich sage vielleicht, weil ein read () nach epoll (), das mit E / POLLIN zurückgegeben wurde, immer noch blockieren kann.

Ihr Code versucht auch, nach EOF zu lesen, und ignoriert alle read () - Fehler vollständig.


Obwohl dies keine direkte Lösung für mein Problem ist, danke für den Kommentar. Mir ist klar, dass dieser Code Fehler aufweist und die EOF-Erkennung in einer weniger abgespeckten Version vorhanden ist (mithilfe von POLLHUP / POLLNVAL). Ich habe jedoch Schwierigkeiten, einen ungepufferten Weg zu finden, um Zeilen aus mehreren Dateideskriptoren zu lesen. Mein repeatProgramm verarbeitet im Wesentlichen NMEA-Daten (zeilenbasiert und ohne Längenindikatoren) aus mehreren Quellen. Da ich Daten aus mehreren Live-Quellen kombiniere, möchte ich, dass meine Lösung ungepuffert ist. Können Sie einen effizienteren Weg vorschlagen, dies zu tun?
Roel Baardman

fwiw, ein Systemaufruf (Lesen) für jedes Byte ist der am wenigsten effiziente Weg. Die EOF-Überprüfung kann durchgeführt werden, indem nur der Rückgabewert von read überprüft wird, ohne dass POLLHUP erforderlich ist (und POLLNVAL wird nur zurückgegeben, wenn Sie eine falsche fd übergeben, nicht an EOF). Aber wie auch immer, bleiben Sie dran. Ich habe die Idee eines ypeeDienstprogramms, das aus mehreren fds liest und sie in ein anderes fd mischt, während Aufzeichnungen erhalten bleiben (Zeilen intakt bleiben).
Pizdelect

Ich habe festgestellt, dass diese Bash-Konstruktion dies tun sollte, aber ich weiß nicht, wie ich stdin darin kombinieren soll: Die { cmd1 & cmd2 & cmd3; } > fileDatei enthält das, was Sie beschreiben. In meinem Fall führe ich jedoch alles von tcpserver (3) aus, daher möchte ich auch stdin (das die Clientdaten enthält) einschließen. Ich bin mir nicht sicher, wie ich das machen soll.
Roel Baardman

1
Es kommt darauf an, was cmd1, cmd2, ... sind. Wenn es sich um nc oder cat handelt und Ihre Daten zeilenorientiert sind, ist die Ausgabe möglicherweise fehlerhaft. Sie erhalten Zeilen, die aus dem Anfang einer von cmd1 gedruckten Zeile und dem Ende einer von cmd2 gedruckten Zeile bestehen.
pizdelect
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.