ทำไมฉันถึงสูญเสียข้อมูลโดยใช้การสร้างท่อ bash นี้


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กับกระบวนการทั้งหมด แต่ดูเหมือนจะไม่ทำงาน
  • ฉันสลับ epoll เพื่อสำรวจความคิดเห็นแล้วไม่ทำงาน
  • เมื่อฉันดูกระแสระหว่าง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<&- ...)หรือไม่
Roel Baardman

5
<(... </dev/null). อย่าใช้0<&-: มันจะทำให้คนแรกopen(2)ที่กลับมา0เป็น fd ใหม่ หากการncสนับสนุนของคุณคุณยังสามารถใช้-dตัวเลือก
mosvy

3

epoll () หรือการสำรวจความคิดเห็น () ที่กลับมาพร้อม E / POLLIN จะบอกคุณว่าการอ่านครั้งเดียว () อาจไม่บล็อก

ไม่ว่าคุณจะสามารถอ่านจำนวนหนึ่งไบต์ได้มากถึงบรรทัดใหม่ตามที่คุณทำ

ฉันบอกว่าอาจเป็นเพราะ read () หลังจาก epoll () ส่งคืนด้วย E / POLLIN อาจยังบล็อกอยู่

รหัสของคุณจะพยายามอ่าน EOF ที่ผ่านมาและละเว้นข้อผิดพลาด read () ทั้งหมด


แม้จะไม่ใช่วิธีการแก้ปัญหาของฉันโดยตรงขอบคุณที่ให้ความคิดเห็น ฉันรู้ว่ารหัสนี้มีข้อบกพร่องและการตรวจสอบ EOF มีอยู่ในรุ่นที่ถอดลงน้อยกว่า (ผ่านการใช้ POLLHUP / POLLNVAL) ฉันพยายามดิ้นรนกับการหาวิธีการอ่านบรรทัดจากตัวอธิบายไฟล์หลาย ๆ ไฟล์ repeatโปรแกรมของฉันกำลังประมวลผลข้อมูล NMEA เป็นหลัก (อิงตามบรรทัดและไม่มีตัวบ่งชี้ความยาว) จากหลายแหล่ง เนื่องจากฉันกำลังรวมข้อมูลจากแหล่งข้อมูลสดหลายแห่งฉันจึงต้องการให้โซลูชันของฉันไม่มีข้อผิดพลาด คุณช่วยแนะนำวิธีที่มีประสิทธิภาพมากกว่านี้ได้ไหม?
Roel Baardman

fwiw, ทำการเรียกระบบ (อ่าน) สำหรับแต่ละไบต์เป็นวิธีที่มีประสิทธิภาพน้อยที่สุด การตรวจสอบ EOF สามารถทำได้โดยเพียงแค่ตรวจสอบค่าส่งคืนของการอ่านไม่จำเป็นต้องใช้ POLLHUP (และ POLLNVAL จะถูกส่งคืนเมื่อคุณส่งมันปลอมเท่านั้นไม่ใช่ใน EOF) แต่ยังไงก็ตามติดตามความคืบหน้า ฉันมีความคิดเกี่ยวกับypeeยูทิลิตี้ที่อ่านจากหลาย fds และผสมให้เป็น fd อีกอันในขณะที่เก็บรักษาบันทึกไว้
pizdelect

ฉันสังเกตเห็นว่าการสร้าง bash นี้ควรทำเช่นนั้น แต่ฉันไม่ทราบวิธีรวม stdin ไว้ในนั้น: { cmd1 & cmd2 & cmd3; } > fileไฟล์จะมีสิ่งที่คุณอธิบาย อย่างไรก็ตามในกรณีของฉันฉันใช้งานทุกอย่างจาก tcpserver (3) ดังนั้นฉันจึงต้องการรวม stdin (ซึ่งมีข้อมูลลูกค้า) ด้วย ฉันไม่แน่ใจว่าจะทำอย่างไร
Roel Baardman

1
ขึ้นอยู่กับว่า cmd1, cmd2, ... คืออะไร หากพวกเขาเป็น nc หรือ cat และข้อมูลของคุณเป็นแบบ line-output ผลลัพธ์อาจผิดรูปแบบ - คุณจะได้รับบรรทัดที่ประกอบด้วยจุดเริ่มต้นของบรรทัดที่พิมพ์โดย cmd1 และจุดสิ้นสุดของบรรทัดที่พิมพ์โดย cmd2
pizdelect
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.