मैं इस बैश पाइप निर्माण का उपयोग करके डेटा क्यों खोता हूं?


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 को अतिक्रमण करता हूं, तो सब कुछ इच्छित तरीके से काम करता है।
  • हालांकि, जब मैं विशिष्ट निर्माण का उपयोग करता हूं, तो मैं स्टड से डेटा (व्यक्तिगत वर्ण) खो देता हूं!

मैंने निम्नलिखित कोशिश की है:

  • मैंने सभी प्रक्रियाओं के बीच pvऔर ./repeatउपयोग करने वाले पाइप पर बफरिंग को अक्षम करने की कोशिश की stdbuf -i0 -o0 -e0है, लेकिन यह काम नहीं करता है।
  • मैंने पोल के लिए एपोल को स्वैप किया है, काम नहीं करता है।
  • जब मैं के बीच धारा को देखो pvऔर ./repeatसाथ tee stream.csv, यह सही लग रहा है।
  • मैं straceदेखता था कि क्या चल रहा है, और मुझे बहुत से सिंगल-बाइट रीड (जैसा कि अपेक्षित था) दिखाई देते हैं और वे यह भी दिखाते हैं कि डेटा गायब हो रहा है।

मुझे आश्चर्य है कि क्या चल रहा है? या आगे की जांच के लिए मैं क्या कर सकता हूं?

जवाबों:


16

क्योंकि ncअंदर की आज्ञा <(...)भी स्टडिन से ही पढ़ेगी।

सरल उदाहरण:

$ 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उसी स्टड के लिए प्रतिस्पर्धा करता है, और ncइसमें से कुछ प्राप्त करता है।


आप सही हे! धन्यवाद! क्या आप स्टिन को डिस्कनेक्ट करने का एक साफ तरीका सुझा सकते हैं <(...)? क्या इससे अच्छा कोई तरीका है <( 0<&- ...)?
Roel Baardman

5
<(... </dev/null)। उपयोग न करें 0<&-: यह नए fd के रूप में सबसे पहले open(2)लौटने का कारण बनेगा 0। यदि आपका ncसमर्थन करता है, तो आप -dविकल्प का उपयोग भी कर सकते हैं ।
मॉसवी

3

एपोल () या पोल () ई / पोली के साथ लौटने पर आपको केवल यह बताएगा कि एक एकल रीड () ब्लॉक नहीं हो सकता है।

ऐसा नहीं है कि आप बहुत सी एक बाइट रीड () एक नईलाइन तक कर पाएंगे, जैसा कि आप करते हैं।

मैं कह सकता हूं क्योंकि ई / पोली के साथ लौटाए गए एपोल () के बाद एक रीड () अभी भी ब्लॉक हो सकता है।

आपका कोड पिछले EOF को पढ़ने का भी प्रयास करेगा, और किसी भी पढ़ने () त्रुटियों को पूरी तरह से अनदेखा कर देगा।


इसके बावजूद मेरी समस्या का सीधा समाधान नहीं है, टिप्पणी करने के लिए धन्यवाद। मुझे लगता है कि इस कोड में खामियां हैं, और EOF का पता एक कम छीन-छांट संस्करण (POLLHUP / POLLNVAL के उपयोग के माध्यम से) में मौजूद है। मैं हालांकि कई फ़ाइल डिस्क्रिप्टर से लाइनों को पढ़ने के लिए एक अप्रभावित रास्ता खोजने के साथ संघर्ष करता हूं। मेरा repeatकार्यक्रम अनिवार्य रूप से कई स्रोतों से NMEA डेटा (लाइन-आधारित और बिना किसी लंबाई के संकेतक) के प्रसंस्करण है। चूंकि मैं कई जीवित स्रोतों से डेटा मिला रहा हूं, इसलिए मैं चाहूंगा कि मेरा समाधान अप्रभावित हो। क्या आप ऐसा करने के लिए अधिक कुशल तरीका सुझा सकते हैं?
Roel Baardman

fwiw, प्रत्येक बाइट के लिए सिस्टम कॉल (रीड) करना सबसे कम संभव तरीका है। EOF की जाँच केवल पढ़ने के रिटर्न मान की जाँच करके की जा सकती है, POLLHUP की कोई आवश्यकता नहीं है (और POLLNVAL केवल तभी लौटाया जाएगा जब आप इसे फर्जी fd पास करेंगे, EOF पर नहीं)। लेकिन वैसे भी, देखते रहें। मेरे पास एक ypeeउपयोगिता का विचार है जो कई एफडी से पढ़ता है और रिकॉर्ड्स को संरक्षित करते हुए (दूसरे को बरकरार रखते हुए) एक और एफडी में मिलाता है।
पिज्ज़लेक्ट

मैंने देखा कि इस बैश कंस्ट्रक्शन को ऐसा करना चाहिए, लेकिन मुझे नहीं पता कि इसमें स्टिंग को कैसे मिलाया जाए: { cmd1 & cmd2 & cmd3; } > fileफाइल में वह होगा जो आप बताते हैं। हालांकि, मेरे मामले में मैं tcpserver (3) से सब कुछ चला रहा हूं, इसलिए मैं स्टडिन (जिसमें क्लाइंट डेटा शामिल है) को भी शामिल करना चाहता हूं। मुझे यकीन नहीं है कि यह कैसे करना है।
Roel Baardman

1
यह cmd1, cmd2, ... पर निर्भर करता है। यदि वे nc या cat हैं और आपका डेटा लाइन-ओरिएंटेड है, तो आउटपुट विकृत हो सकता है - आपको लाइनें मिलेंगी जिसमें cmd1 द्वारा मुद्रित लाइन की शुरुआत और cmd2 द्वारा मुद्रित लाइन के अंत शामिल होंगे।
पिज्ज़लेक्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.