web-dev-qa-db-ja.com

ファイルまたは標準入力から読み取る

ファイル名を受け入れるか、stdinから読み取るユーティリティを作成しています。

Stdinが存在するかどうかを確認する最も堅牢で最速の方法を知りたい(データがプログラムにパイプされている)場合は、そのデータを読み取ります。存在しない場合は、ファイル名で処理が行われます与えられた。 stdinのサイズについて次のテストを使用してみましたが、実際のファイルではなくストリームであるため、思ったとおりに機能せず、常に-1。私はいつも!= EOF=の間に一度に入力1文字を読むことができることを知っていますまた、プログラムの残りの部分はシームレスに機能します。また、前のプログラムによってストリームが閉じられるまで、そのサイズを知ることができるようにしたいと思います。

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
27
Ryan

まず、errnofseekのような失敗時に設定されるftellをチェックして、プログラムに何が間違っているのかを尋ねます。

その他(tonioおよびLatinSuD)は、stdinの処理とファイル名のチェックの誤りを説明しています。つまり、最初にargc(引数カウント)をチェックして、if (argc > 1)が指定されているコマンドラインパラメーターがあるかどうかを確認し、-stdinを意味する特殊なケースとして扱います。

パラメーターが指定されていない場合、入力はstdinstream not file)からの(進行中)であり、fseek関数が失敗すると想定します。

ファイルオンディスク指向のライブラリー関数(つまり、fseekおよびftell)を使用できないストリームの場合、読み取ったバイト数(末尾の改行文字を含む)をカウントするだけです[〜#〜] eof [〜#〜](ファイルの終わり)を受信するまで。

大きなファイルで使用する場合は、fgetsをchar配列に使用して(テキスト)ファイルのバイトをより効率的に読み取ることにより、高速化できます。バイナリファイルの場合は、fopen(const char* filename, "rb")を使用し、fgetc/fgetsの代わりにfreadを使用する必要があります。

また、バイトカウントメソッドを使用してストリームからの読み取り時にエラーを検出する場合は、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;
}
15
mctylr

あなたはそれを間違っていると考えています。

あなたがやろうとしていること:

stdinが存在する場合はそれを使用し、そうでない場合はユーザーがファイル名を指定したかどうかを確認します。

代わりにすべきこと:

ユーザーがファイル名を指定する場合は、ファイル名を使用します。それ以外の場合は、stdinを使用します。

すべてを読み取ってバッファリングしない限り、着信ストリームの全長を知ることはできません。パイプを逆方向に探索することはできません。これは、パイプの動作の制限です。パイプはすべてのタスクに適しているわけではなく、場合によっては中間ファイルが必要です。

22
LatinSuD

たとえば、これがcatユーティリティでどのように実行されるかを確認することができます。

コード here を参照してください。引数としてファイル名がない場合、または「-」の場合、stdinが入力に使用されます。 stdinは、そこにデータがプッシュされない場合でも存在します(ただし、読み取り呼び出しは永遠に待機する場合があります)。

5
tonio

ユーザーがファイル名を指定しない限り、stdinから読み取ることができますか?

そうでない場合は、特別な「ファイル名」-を「stdinから読み取る」という意味として扱います。ユーザーは、データをパイプする場合は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 <fileを実行しているユーザーは処理できませんが

Stdinが端末/コンソールであるかどうかも確認できます

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

必要なのは、stdinが存在するかどうかではなく、端末に接続されているかどうかを知ることです。常に存在しますが、シェルを使用して何かをパイプしたり、ファイルを読み込んだりすると、端末に接続されません。

Termios.h関数を使用して、ファイル記述子が端末に接続されていることを確認できます。

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

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

これは、stdinの端末属性を取得しようとします。パイプに接続されていない場合、ttyに接続され、tcgetattr関数呼び出しは成功します。パイプを検出するために、tcgetattrの障害をチェックします。

0
user3114703

feofを使用してファイルの終わりをテストするだけで十分だと思います。

0
Jens Gustedt