Как правильно фредить и писать от & к трубе - PullRequest
0 голосов
/ 13 апреля 2019

У меня есть этот код, который действует как канал между двумя вызовами оболочки.

Он читает из канала и записывает в другой.

#include <stdio.h>
#include <stdlib.h>


#define BUFF_SIZE (0xFFF)

/*
 *  $ cat /tmp/redirect.txt |less
 */
int main(void)
{
    FILE    *input;
    FILE    *output;
    int     c;
    char    buff[BUFF_SIZE];
    size_t  nmemb;

    input   = popen("cat /tmp/redirect.txt", "r");
    output  = popen("less", "w");
    if (!input || !output)
        exit(EXIT_FAILURE);

#if 01
    while ((c = fgetc(input))  !=  EOF)
        fputc(c, output);
#elif 01
    do {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    } while (nmemb);
#elif 01
    while (feof(input) != EOF) {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    }
#endif
/*
 * EDIT: The previous implementation is incorrect:
 * feof() return non-zero if EOF is set
 * EDIT2:  Forgot the !.  This solved the problem.
 */
#elif 01
    while (feof(input)) {
        nmemb   = fread(buff, 1, sizeof(buff), input);
        fwrite(buff, 1, nmemb, output);
    }
#endif

    pclose(input);
    pclose(output);

    return  0;
}

Я хочу, чтобы он был эффективным, поэтому я хочу реализовать его с помощью fread() & fwrite(). Есть 3 способа, которыми я пытался.

Первый реализован с помощью fgetc() & fputc(), поэтому он будет очень медленным. Однако он работает нормально, потому что проверяет EOF, поэтому он будет ждать, пока cat (или любой другой вызов оболочки, который я использую) завершит свою работу.

Второй - быстрее, но меня беспокоит, что я не проверяю EOF, поэтому, если есть какой-то момент, когда канал пуст (но вызов оболочки не завершен, поэтому он может быть не пустым). в будущем) закроет трубу и закончится.

Третья реализация - это то, что я хотел бы сделать, и она относительно работает (весь текст получен less), но по какой-то причине он застревает и не закрывает канал (кажется, что он никогда не получает EOF).

РЕДАКТИРОВАТЬ: Третья реализация глючит. Четвертый пытается решить ошибку, но теперь less ничего не получает.

Как это правильно сделать?

Ответы [ 2 ]

1 голос
/ 15 апреля 2019

Прежде всего, скажу, что я думаю, что у вас больше проблем с буферизацией, чем с эффективностью. Это распространенная проблема при первом обращении к пакету stdio.

Во-вторых, лучшей (и самой простой) реализацией простого копира данных от ввода к выводу является следующий фрагмент (скопированный из первого издания K & R).

while((c = fgetc(input)) != EOF) 
    fputc(c, output);

(ну, не буквальная копия, так как там K & R использует stdin и stdout как FILE* дескрипторы, и они используют более простые вызовы getchar(); и putchar(c);.) Когда вы пытаетесь сделать лучше чем это, обычно вы принимаете некоторые ложные предположения, такие как ошибка отсутствия буферизации или количество системных вызовов.

stdio выполняет полную буферизацию, когда стандартный вывод представляет собой канал (действительно, он выполняет полную буферизацию всегда, кроме случаев, когда файловый дескриптор дает true для вызова функции isatty(3)), поэтому вы В случае, если вы хотите увидеть вывод, как только он станет доступен, по крайней мере, не нужно буферизовать вывод (с чем-то вроде setbuf(out, NULL); или fflush()) вашего вывода в какой-то момент, так что он не получит буферизируется в выходных данных, пока вы ожидаете входных данных для получения дополнительных данных.

Кажется, что вы видите, что вывод для программы less(1) не виден, потому что он буферизируется во внутренних компонентах вашей программы. И это именно то, что происходит ... предположим, что вы кормите свою программу (которая, несмотря на обработку отдельных символов, выполняет полную буферизацию), не получает никакого ввода, пока не будет заполнен полный входной буфер (BUFSIZ символов) подал к нему. Затем в цикле выполняется много одиночных вызовов fgetc(), а в цикле выполняется много вызовов fputc() (ровно BUFSIZ вызовов каждый), и буфер заполняется на выходе. Но этот буфер не записан, потому что для принудительного сброса нужен еще один символ. Таким образом, пока вы не получите первые два BUFSIZ фрагмента данных, вы ничего не получите в less(1).

Простой и эффективный способ - проверить после fputc(c, out);, является ли символ \n, и сбросить вывод с fflush(out); в этом случае, и поэтому вы будете записывать строку вывода за раз.

fputc(c, out);
if (c == '\n') fflush(out);

Если вы ничего не делаете, буферизация выполняется в BUFSIZ блоках, и обычно, не раньше, чем у вас будет такое количество данных на выходной стороне. И всегда помните fclose() вещей (ну, это обрабатывается stdio), иначе вы можете потерять вывод в случае прерывания вашего процесса.

ИМХО код, который вы должны использовать:

while ((c = fgetc(input))  !=  EOF) {
    fputc(c, output);
    if (c == '\n') fflush(output);
}
fclose(input);
fclose(output);

для лучшей производительности, без ненужной блокировки выходных данных в буфере.

Кстати, выполнение fread() и fwrite() одного символа - пустая трата времени и способ усложнить ситуацию (и подвержен ошибкам). fwrite() одного символа не исключает использование буферов, поэтому вы не получите большей производительности, чем fputc(c, output);.

Кстати (бис), если вы хотите сделать свою собственную буферизацию, не вызывайте функции stdio, просто используйте вызовы read(2) и write(2) в обычных дескрипторах системных файлов. Хороший подход:

int input_fd = fileno(input); /* input is your old FILE * given by popen() */
int output_fd = fileno(output);

while ((n = read(input_fd, your_buffer, sizeof your_buffer)) > 0) {
    write(output_fd, your_buffer, n);
}
switch (n) {
case 0: /* we got EOF */
    ...
    break;
default: /* we got an error */
    fprintf(stderr, "error: read(): %s\n", strerror(errno));
    ...
    break;
} /* switch */

но это разбудит вашу программу только тогда, когда буфер полностью заполнен данными, или данных больше нет.

Если вы хотите передать свои данные в less(1), как только у вас будет на одну строку меньше, вы можете полностью отключить входной буфер с помощью:

setbuf(input, NULL);
int c; /* int, never char, see manual page */
while((c == fgetc(input)) != EOF) {
    putc(c, output);
    if (c == '\n') fflush(output);
}

И вы получите less(1), работающий, как только вы создадите одну строку выходного текста.

Что именно вы пытаетесь сделать? (Это было бы неплохо знать, поскольку вы, похоже, заново изобретаете программу cat(1), но с ограниченной функциональностью)

0 голосов
/ 13 апреля 2019

Самое простое решение:


while (1) {
    nmemb = fread(buff, 1, sizeof buff, input);
    if (nmemb < 1) break; 
    fwrite(buff, 1, nmemb, output);
}

Аналогично, для случая getc():


while (1) {
    c = getc(input);
    if (c == EOF) break;
    putc(c, output);
}

Замена fgetc() на getc() даст производительность, эквивалентную случаю fread(). (getc() (часто) будет макросом, избегая издержек при вызове функции). [просто взгляните на сгенерированную сборку.

...