从文件或标准输入读取

Posted

技术标签:

【中文标题】从文件或标准输入读取【英文标题】:Read from file or stdin 【发布时间】:2011-03-30 12:49:37 【问题描述】:

我正在编写一个接受文件名或从标准输入读取的实用程序。

我想知道检查标准输入是否存在(数据正在通过管道传输到程序)以及如果存在则读取该数据的最强大/最快的方法。如果它不存在,则将进行处理在给定的文件名上。我已经尝试使用以下测试来测试stdin 的大小,但我相信因为它是一个流而不是实际文件,所以它不像我想象的那样工作,它总是打印-1。我知道我总是可以在 != EOF 时一次读取输入的 1 个字符,但我想要一个更通用的解决方案,所以如果标准输入存在,我可以最终得到 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

【问题讨论】:

【参考方案1】:

你想错了。

你想做什么:

如果标准输入存在就使用它,否则检查用户是否提供了文件名。

你应该做什么:

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

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

【讨论】:

【参考方案2】:

首先,请程序通过检查设置为失败的errno(例如在fseekftell 期间)来告诉您出了什么问题。

其他人(tonio 和 LatinSuD)解释了处理标准输入与检查文件名的错误。即首先检查argc(参数计数),看是否有任何命令行参数指定if (argc > 1),将-视为特例含义stdin

如果未指定参数,则假设输入(将要)来自stdin,这是一个非文件,fseek 函数在其上失败。

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

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

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

以下示例应符合 C99 且可移植。

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

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;

【讨论】:

【参考方案3】:

例如,您可能想看看这是如何在 cat 实用程序中完成的。

见代码here。 如果没有文件名作为参数,或者它是“-”,那么stdin 用于输入。 stdin 将在那里,即使没有数据推送到它(但是,您的读取调用可能会永远等待)。

【讨论】:

请注意,您可以使用cat fileA - fileBfileAfileB 的内容包围在标准输入中。它不限于成为唯一的论据。对于某些程序(例如paste),您可以多次使用-(有时包括cat,有时甚至有用)。例如,paste - - - 根据从标准输入读取的数据创建 3 列输出,第一列读取一行,第二列读取另一行,第三列读取第三行。【参考方案4】:

除非用户提供文件名,否则您只能从标准输入读取?

如果不是,则将特殊的“文件名”- 视为“从标准输入读取”的意思。如果用户想通过管道将数据传输给它,则用户必须像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 &lt;file

您还可以检查标准输入是否是终端/控制台

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

【讨论】:

【参考方案5】:

我认为,只需使用 feof 测试文件结尾即可。

【讨论】:

feof 很棘手,因为它要求您事先尝试从流中读取并失败。 (如果你这样做,你不妨检查失败原因。)你建议如何在这种情况下使用它也不是很明显。【参考方案6】:

请注意,您想要知道标准输入是否连接到终端,而不是是否存在。它始终存在,但是当您使用 shell 向其中传输内容或读取文件时,它并没有连接到终端。

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

#include <termios.h>
#include <stdbool.h>

bool stdin_is_a_pipe(void)

    struct termios t;
    return (tcgetattr(STDIN_FILENO, &t) < 0);

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

【讨论】:

必须添加 #include 才能定义 STDIN_FILENO

以上是关于从文件或标准输入读取的主要内容,如果未能解决你的问题,请参考以下文章

使用 Qt 从标准输入异步读取

从线程中的标准输入读取以写入 c 中的文件

C语言,程序读取标准输入是啥意思?

从标准输入读取所有文本到字符串

如何从标准输入读取 dask 数据帧?

IO文件