[英]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在那里將stdin
和stdout
用作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.