从文件或标准输入读取

我正在编写一个实用程序,它接受文件名或从stdin读取。

我想知道检查stdin是否存在的最强大/最快的方法(数据是否通过管道传输到程序),如果是,则读取该数据。如果不存在,则处理将在文件名上进行给出。 我已经尝试使用以下测试stdin大小,但我相信因为它是一个流而不是一个实际的文件,它不起作用,因为我怀疑它会,它总是打印-1 。 我知道我总是可以一次读取输入1个字符!= EOF但是我想要一个更通用的解决方案,所以如果stdin存在,我最终会得到fd或FILE *所以程序的其余部分将无缝运行。 我希望能够知道它的大小,等待前一个程序关闭流。

 long getSizeOfInput(FILE *input){ long retvalue = 0; fseek(input, 0L, SEEK_END); retvalue = ftell(input); fseek(input, 0L, SEEK_SET); return retvalue; } int main(int argc, char **argv) { printf("Size of stdin: %ld\n", getSizeOfInput(stdin)); exit(0); } 

终奌站:

 $ echo "hi!" | myprog Size of stdin: -1 

首先,要求程序通过检查errno设置的errno来告诉你出了什么问题,例如在fseekftell期间。

其他人(tonio和LatinSuD)已经解释了处理stdin与检查文件名的错误。 即,首先检查argc (参数计数)以查看是否存在指定的任何命令行参数if (argc > 1) ,处理-作为特殊情况表示stdin

如果没有指定参数,则假设输入(来)来自stdin ,这是一个而不是文件,并且fseek函数在其上失败。

对于流,您不能使用面向磁盘文件的库函数(即fseekftell ),您只需计算读取的字节数(包括尾随换行符),直到接收到EOF (结束时)文件)。

对于大文件的使用,您可以通过将fgets用于char数组来加快速度,以便更有效地读取(文本)文件中的字节。 对于二进制文件,您需要使用fopen(const char* filename, "rb")并使用fread而不是fgetc/fgets

在使用字节计数方法检测从流中读取时的任何错误时,您还可以检查for feof(stdin) / ferror(stdin)

以下示例应符合C99和便携性。

 #include  #include  #include  #include  long getSizeOfInput(FILE *input){ long retvalue = 0; int c; if (input != stdin) { if (-1 == fseek(input, 0L, SEEK_END)) { fprintf(stderr, "Error seek end: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (-1 == (retvalue = ftell(input))) { fprintf(stderr, "ftell failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (-1 == fseek(input, 0L, SEEK_SET)) { fprintf(stderr, "Error seek start: %s\n", strerror(errno)); exit(EXIT_FAILURE); } } else { /* for stdin, we need to read in the entire stream until EOF */ while (EOF != (c = fgetc(input))) { retvalue++; } } return retvalue; } int main(int argc, char **argv) { FILE *input; if (argc > 1) { if(!strcmp(argv[1],"-")) { input = stdin; } else { input = fopen(argv[1],"r"); if (NULL == input) { fprintf(stderr, "Unable to open '%s': %s\n", argv[1], strerror(errno)); exit(EXIT_FAILURE); } } } else { input = stdin; } printf("Size of file: %ld\n", getSizeOfInput(input)); return EXIT_SUCCESS; } 

你认为这是错的。

你想做什么:

如果stdin存在使用它,否则检查用户是否提供了文件名。

你应该做的是:

如果用户提供文件名,则使用文件名。 否则使用标准输入。

除非您全部阅读并保持缓冲,否则您无法知道传入流的总长度。 你无法向后寻找管道。 这是管道工作方式的限制。 管道不适合所有任务,有时需要中间文件。

例如,您可能想要了解如何在cat实用程序中完成此操作。

请参阅此处的代码 如果没有文件名作为参数,或者它是“ – ”,则stdin用于输入。 stdin将在那里,即使没有数据被推送到它(但是,你的读取呼叫可能永远等待)。

除非用户提供文件名,否则你可以从stdin读取?

如果没有,请将特殊的“文件名”视为“从标准输入读取”。 用户必须像cat file | myprogram -一样启动程序 cat file | myprogram -如果他想管道数据, myprogam file如果他想要从文件读取。

 int main(int argc,char *argv[] ) { FILE *input; if(argc != 2) { usage(); return 1; } if(!strcmp(argv[1],"-")) { input = stdin; } else { input = fopen(argv[1],"rb"); //check for errors } 

如果您使用* nix,则可以检查stdin是否为fifo:

  struct stat st_info; if(fstat(0,&st_info) != 0) //error } if(S_ISFIFO(st_info.st_mode)) { //stdin is a pipe } 

虽然这不会处理用户做myprogram

您还可以检查stdin是否是终端/控制台

 if(isatty(0)) { //stdin is a terminal } 

我想,只是用feof测试文件结尾。

请注意,您想要知道stdin是否连接到终端,而不是它是否存在。 它总是存在但是当你使用shell管道内容或读取文件时,它没有连接到终端。

您可以通过termios.h函数检查文件描述符是否已连接到终端:

 #include  #include  bool stdin_is_a_pipe(void) { struct termios t; return (tcgetattr(STDIN_FILENO, &t) < 0); } 

这将尝试获取stdin的终端属性。 如果它没有连接到管道,它将附加到tty并且tcgetattr函数调用将成功。 为了检测管道,我们检查tcgetattr故障。