簡體   English   中英

如何從管道正確讀取和寫入

[英]How to properly fread & fwrite from & to a pipe

我有這段代碼,它充當兩個shell調用之間的管道。

它從管道讀取,然后寫入另一個管道。

#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 (或我使用的任何shell調用)完成其工作。

第二個比較快,但是我擔心我不檢查EOF所以是否有任何時刻管道是空的(但是shell調用還沒有完成,所以將來可能不會是空的),它將關閉管道並結束。

第三種實現是我想做的,它相對有效(所有文本都被less接收),但是由於某種原因,它被卡住了並且沒有關閉管道(好像它永遠都無法獲得EOF)。

編輯:第三個實現是越野車。 第四種試圖解決該錯誤,但是現在less東西什么也沒有得到。

應該如何正確地做到這一點?

首先,要說的是,我認為您遇到的問題更多是緩沖問題,而不是效率問題。 第一次處理stdio軟件包時,這是一個常見問題。

其次,從輸入到輸出的簡單數據復印機的最佳(也是最簡單)實現是以下代碼段(從K&R的第一版復制)。

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

(好吧,不是普通的副本,K&R在那里將stdinstdout用作FILE*描述符,並且它們使用更簡單的getchar();putchar(c);調用。)當您嘗試做得更好時,通常您會由於缺乏緩沖或系統調用數量的謬誤,會導致一些錯誤的假設。

當標准輸出是管道時, stdio會進行完全緩沖 (實際上,除非文件描述符將isatty(3)函數調用的true設置為true ,否則它將始終進行完全緩沖),因此,如果要查看輸出,則應該這樣做一旦可用,至少在某個時候沒有輸出緩沖(例如setbuf(out, NULL);fflush() ),因此在等待時不會在輸出中緩沖在輸入中獲取更多數據。

看起來是這樣,您會看到less(1)程序的輸出是不可見的,因為它被緩沖在程序的內部。 這就是正在發生的事情...假設您饋入程序(盡管處理了單個字符,但仍在進行完全緩沖)在完全輸入緩沖區( BUFSIZ字符)被饋入之前不會得到任何輸入它。 然后,在循環中完成許多單個fgetc()調用,而在循環中完成許多fputc()調用(每個都恰好BUFSIZ調用),並在輸出處填充緩沖區。 但是不會寫入此緩沖區,因為它還需要一個char來強制進行刷新。 因此,直到獲得前兩個BUFSIZ數據塊,您都不BUFSIZ任何內容寫入less(1)

一種簡單而有效的方法是檢查fputc(c, out); 如果char是\\n ,並用fflush(out);刷新輸出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);

為獲得最佳性能,同時不會不必要地阻塞緩沖區中的輸出數據。

BTW執行一個字符的fread()fwrite()既浪費時間,又使事情復雜化(而且容易出錯)。 一個字符的fwrite()不會避免使用緩沖區,因此不會比使用fputc(c, output);獲得更高的性能fputc(c, output);

BTW(bis)如果要自己緩沖,請不要調用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)程序,但功能有所減少)

最簡單的解決方案:


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);
}

getc()替換fgetc()將獲得與fread()情況等效的性能。 getc()通常是一個宏,避免了函數調用的開銷)。 [只看生成的程序集。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM