【问题标题】:Why fgets hangs when program closes and data piped from tail -f?为什么当程序关闭并且数据从 tail -f 传输时 fgets 挂起?
【发布时间】:2017-07-16 00:24:51
【问题描述】:

我正在使用tail -f 读取日志并将数据传送到我的ncurses 程序。

tail -f somelog | ./a.out

它工作正常,除了当我按下 q 时,程序并没有退出,它只是挂起 直到一个字节被写入正在被拖尾的文件。使用cat somelog | ./a.out时不会出现这种情况,我猜是因为没有被关注。

看起来while(fgets(buf, 1024, input) 行引起了问题。任何想法为什么以及如何解决它?谢谢

   // gcc app.c -lncurses
// tail -f logfile | ./a.out

#include <stdio.h>
#include <ncurses.h>
#include <fcntl.h>
#include <string.h>

void print_status(WINDOW * win, const char *str) {
    init_pair(1, COLOR_RED, COLOR_WHITE);
    clear();
    mvwhline(win, 0, 0, ' ', COLS);
    mvwaddnstr(win, 0, 0, str, COLS);
    wrefresh(win);
}

void print_line(WINDOW * win, const char *str, int *i) {
    init_pair(2, COLOR_RED, COLOR_BLACK);
    if (*i > LINES - 2) {
        *i = 1;
        wclear(win);
    }
    mvwprintw(win, (*i)++, 0, str);
    wrefresh(win);
}

int main() {
    WINDOW *menu_win, *wi;
    int i = 1, q = 1, fd1, fd2, is_atty = 1;
    char buf[1024];

    initscr();
    clear();
    halfdelay(5);

    menu_win = newwin(1, COLS, 0, 0);
    wi = newwin(LINES - 1, COLS, 1, 0);
    keypad(menu_win, TRUE);

    FILE *input = stdin;

    if(!isatty(fileno(stdin))) {
        is_atty = 0;
        fd1 = open("/dev/tty", O_RDONLY);
        fd2 = dup(fileno(stdin));
        input = fdopen(fd2, "r");
        freopen("/dev/tty", "r", stdin);
        if(fileno(stdin) != 0)    /* from ncurses dialog */
            (void)dup2(fileno(stdin), 0);
        close(fd1);
        int flags = fcntl(fd2, F_GETFL);
        // non-blocking fgets so getch works fine 
        fcntl(fd2, F_SETFL, flags|O_NONBLOCK);
    }

    print_status(menu_win, "Use arrow keys to go up and down");
    while(q) {
        switch(wgetch(menu_win)) {
            case KEY_UP:
                print_status(menu_win, "Moved up");
                break;
            case KEY_DOWN:
                print_status(menu_win, "Moved down");
                break;
            case 'q':
                q = 0;
                break;
            default:
                if(is_atty) break;
                while(fgets(buf, 1024, input) != NULL) {
                    print_line(wi, buf, &i);
                }
                break;
        }
    }
    fclose(input);
    close(fd2);
    endwin();
    return 0;
}

【问题讨论】:

  • cat 发送一个EOFtail -f 没有,它只是保持管道打开。如果它是tail -100,它将表现得像cat。但是,当您按q 时,不会调用您识别的行。你确实关闭了input,但你没有关闭fd2
  • @alvits 谢谢我编辑了问题并添加了close(fd2)
  • default: case 仅在您按任意键时执行。当您按下q 时,它会执行case 'q':,这随后会导致while 循环终止。导致它阻塞的不是fgets()。管道到达return 0; 时仍处于打开状态。运行tail -f somelog | strace ./a.out。查看您的代码是否没有关闭管道。 tail 将在收到 SIGPIPE 时终止。请记住,您有多个管道副本。在case 'q': 块中打印一些内容。这将帮助您进行故障排除。
  • @alvits 感谢您的回复。做一个strace,我看到q存在循环并到达close(fs2),我还看到fd1 = 4fd2 = 5,然后关闭4,然后关闭0,最后关闭5 .我看不到哪个 fd 仍然打开。我忽略了什么吗?
  • 您的问题确实没有任何解决方案。以tail -f /etc/hosts | lstail -f /etc/hosts | /bin/echo ok 为例,两者都会挂起,直到您终止tail -f。简而言之,当管道的写入端仍然打开时,读取器将退出,但控制权不会返回到外壳,直到您终止生产者。而straceing 2个样本说明进程已经成功退出,exit_group(0) = ? +++ exited with 0 +++,只有tail -f没有。

标签: c pipe stdin fgets tail


【解决方案1】:

一旦您的代码调用fgets(),它将在fgets() 中保持阻塞状态,直到更多输入到达 - 或管道关闭。

cat somelog | ./a.out 不会发生这种情况,因为正如您所怀疑的那样,cat 不会等待更多数据写入文件。 cat 继续发送数据然后退出,关闭管道并导致fgets() 返回它缓冲的数据,它正在等待更多或NULL 如果没有读取数据。 tail -f ... 在到达文件末尾时不会退出并关闭管道 - tail -f ... 只是等待更多数据写入它正在拖尾的文件,所以你对 fgets() 的调用就在那里。

那为什么?因为fgets() 在获得换行符或 EOF 之前不会返回。 Per the Linux man page (assuming you're running on Linux):

fgets() 最多从 size 个字符中读取 并将它们存储到 s 指向的缓冲区中。阅读停止后 EOFnewline。 ...

【讨论】:

  • O_NONBLOCK 的调用不会让fgets 返回吗?有没有办法来解决这个问题?谢谢
  • @user1024718 我认为这取决于实现。弄乱FILE * 下的文件描述符属性似乎有问题。 fgets() 应该阻塞直到 EOF 或换行符。见stackoverflow.com/questions/6055702/…
  • 这是一个很好的观点,不过,我有点犹豫是否要添加 select(),因为它会阻塞,我正在使用 getch() 来让键盘工作。
猜你喜欢
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 2015-03-02
  • 2021-12-23
相关资源
最近更新 更多