web-dev-qa-db-fra.com

Lire du fichier ou stdin

J'écris un utilitaire qui accepte un nom de fichier ou lit à partir de stdin.

Je voudrais connaître le moyen le plus robuste/le plus rapide de vérifier si stdin existe (les données sont acheminées vers le programme) et, le cas échéant, de lire ces données. Si elles n'existent pas, le traitement aura lieu sur le nom du fichier. donné. J'ai essayé d'utiliser le test suivant pour la taille de stdin, mais comme il s'agit d'un flux et non d'un fichier réel, cela ne fonctionne pas comme je le pensais et le code -1 est toujours imprimé. Je sais que je peux toujours lire le caractère saisi 1 à la fois avec! = EOF, mais je voudrais une solution plus générique pour pouvoir me retrouver avec un fd ou un FILE * si stdin existe, le reste du programme fonctionnera de manière transparente. J'aimerais aussi pouvoir connaître sa taille, en attendant que le flux ait été fermé par le programme précédent.

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

Terminal:

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

Tout d’abord, demandez au programme de vous dire ce qui ne va pas en cochant la errno, qui est définie sur l’échec, comme par exemple pendant fseek ou ftell.

D'autres (tonio et LatinSuD) ont expliqué l'erreur de traitement de stdin par rapport à la recherche d'un nom de fichier. À savoir, vérifiez d'abord argc (nombre d'arguments) pour voir s'il existe des paramètres de ligne de commande spécifiés if (argc > 1), en traitant - comme un cas spécial signifiant stdin

Si aucun paramètre n'est spécifié, supposons que l'entrée (va) provienne de stdin, qui est un fichier stream not, et que la fonction fseek échoue.

Dans le cas d'un flux où vous ne pouvez pas utiliser les fonctions de bibliothèque orientées fichier sur disque (c'est-à-dire fseek et ftell), il vous suffit de compter le nombre d'octets lus (y compris les caractères de fin de ligne suivants) jusqu'à la réception deEOF(fin de fichier).

Pour une utilisation avec des fichiers volumineux, vous pouvez l'accélérer en utilisant fgets dans un tableau de caractères pour une lecture plus efficace des octets dans un fichier (texte). Pour un fichier binaire, vous devez utiliser fopen(const char* filename, "rb") et utiliser fread au lieu de fgetc/fgets.

Vous pouvez également vérifier la présence de feof(stdin)/ferror(stdin) lors de l’utilisation de la méthode de comptage d’octets pour détecter les erreurs éventuelles lors de la lecture d’un flux.

L'échantillon ci-dessous doit être conforme à C99 et portable.

#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

Vous pensez que c'est faux.

Ce que vous essayez de faire:

Si stdin existe, utilisez-le, sinon vérifiez si l'utilisateur a fourni un nom de fichier.

Ce que vous devriez faire à la place:

Si l'utilisateur fournit un nom de fichier, utilisez-le. Sinon utilisez stdin.

Vous ne pouvez pas connaître la longueur totale d'un flux entrant à moins de tout lire et de le garder en mémoire tampon. Vous ne pouvez tout simplement pas chercher en arrière dans les tuyaux. Ceci est une limitation du fonctionnement des tuyaux. Les pipes ne conviennent pas à toutes les tâches et des fichiers intermédiaires sont parfois nécessaires.

22
LatinSuD

Vous voudrez peut-être voir comment cela se fait dans l'utilitaire cat, par exemple.

Voir le code ici . S'il n'y a pas de nom de fichier comme argument, ou s'il s'agit de "-", alors stdin est utilisé pour l'entrée .stdin sera présent, même si aucune donnée n'y est insérée ( mais alors, votre appel de lecture peut attendre pour toujours). 

5
tonio

Vous pouvez simplement lire à partir de stdin à moins que l'utilisateur ne fournisse un nom de fichier?

Sinon, traitez le "nom de fichier" spécial - comme signifiant "lu à partir de stdin". L'utilisateur devrait démarrer le programme de la manière suivante: cat file | myprogram - s'il souhaite y diriger les données, et myprogam file s'il souhaite que ces données soient lues dans un fichier.

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
    }

Si vous êtes sur * nix, vous pouvez vérifier si stdin est un fifo:

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

Bien que cela ne gèrera pas l'utilisateur en train de faire myprogram <file

Vous pouvez également vérifier si stdin est un terminal/une console

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

Notez que ce que vous voulez, c'est savoir si stdin est connecté à un terminal ou non, pas s'il existe. Il existe toujours, mais lorsque vous utilisez le shell pour y insérer quelque chose ou lire un fichier, celui-ci n'est pas connecté à un terminal.

Vous pouvez vérifier qu'un descripteur de fichier est connecté à un terminal via les fonctions termios.h:

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

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

Cela va essayer de récupérer les attributs de terminal de stdin. S'il n'est pas connecté à un tuyau, il est attaché à un terminal et l'appel de la fonction tcgetattr aboutira. Afin de détecter un tuyau, nous vérifions la défaillance de tcgetattr. 

0
user3114703

Je pense que le simple fait de tester la fin du fichier avec feof conviendrait.

0
Jens Gustedt