이 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./repeatwith 사이의 스트림을 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갔습니까? netcat을 통해.

$ cat /tmp/foo
text

당신의 프로그램 nc은 같은 표준을 위해 경쟁하고 nc일부를 얻습니다.


네가 옳아! 감사! 에서 stdin 연결을 끊을 수있는 깔끔한 방법을 제안 할 수 있습니까 <(...)? 보다 좋은 방법이 <( 0<&- ...)있습니까?
Roel Baardman

5
<(... </dev/null). 사용하지 마십시오 0<&-: 첫 번째 open(2)0새로운 fd 로 돌아갑니다 . nc지원하는 경우 -d옵션 을 사용할 수도 있습니다 .
mosvy 2016 년

3

E / POLLIN과 함께 반환되는 epoll () 또는 poll ()은 단일 read () 차단되지 않을뿐 임을 알려줍니다 .

당신이하는 것처럼 줄 바꿈까지 많은 1 바이트 read ()를 수행 할 수는 없습니다.

E / POLLIN으로 반환 된 epoll () 이후의 read ()가 여전히 차단 될 수 있기 때문에 말할 수 있습니다 .

코드는 또한 EOF를 지나서 읽으려고 시도하며 read () 오류를 완전히 무시합니다.


이것이 내 문제에 대한 직접적인 해결책은 아니지만 의견을 보내 주셔서 감사합니다. 이 코드에는 결함이 있으며 EOF 탐지는 덜 벗겨진 버전 (POLLHUP / POLLNVAL을 사용하여)에 존재한다는 것을 알고 있습니다. 그래도 여러 파일 설명자에서 줄을 읽는 버퍼되지 않은 방법을 찾는 데 어려움을 겪고 있습니다. 내 repeat프로그램은 본질적으로 여러 소스에서 NMEA 데이터 (라인 기반 및 길이 표시기가 없음)를 처리하고 있습니다. 여러 라이브 소스의 데이터를 결합하고 있으므로 솔루션을 버퍼링하지 않기를 바랍니다. 보다 효율적인 방법을 제안 할 수 있습니까?
Roel Baardman 2018 년

각 바이트마다 시스템 호출 (읽기)을 수행하는 것이 가장 효율적인 방법은 아닙니다. EOF 검사는 POLLHUP 필요없이 read의 반환 값만 확인하면됩니다 (그리고 POLLNVAL은 EOF가 아닌 가짜 fd를 전달할 때만 반환됩니다). 어쨌든 계속 지켜봐 주시기 바랍니다. ypee레코드를 유지하면서 여러 fds에서 읽고 다른 fd로 혼합 하는 유틸리티에 대한 아이디어가 있습니다 (행을 그대로 유지).
pizdelect 2016 년

이 bash 구성이 그렇게해야한다는 것을 알았지 만 stdin을 결합하는 방법을 모르겠습니다. { cmd1 & cmd2 & cmd3; } > file파일에는 설명 한 내용이 포함됩니다. 그러나 필자의 경우 tcpserver (3)에서 모든 것을 실행하므로 stdin (클라이언트 데이터가 포함되어 있음)도 포함하고 싶습니다. 어떻게 해야할지 모르겠습니다.
Roel Baardman 2014 년

1
cmd1, cmd2, ...가 무엇인지에 따라 다릅니다. nc 또는 cat이고 데이터가 행 지향 인 경우 출력 형식이 잘못 될 수 있습니다. cmd1에 의해 인쇄 된 행의 시작과 cmd2에 의해 인쇄 된 행의 끝으로 구성된 행을 얻게됩니다.
pizdelect
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.