Leer desde archivo o stdin

6 minutos de lectura

avatar de usuario
Ryan

Estoy escribiendo una utilidad que acepta un nombre de archivo o lee desde stdin.

Me gustaría saber la forma más robusta/rápida de verificar si existe stdin (los datos se están canalizando al programa) y, de ser así, leer esos datos. Si no existe, el procesamiento se llevará a cabo en el nombre del archivo. dado. He intentado usar la siguiente prueba para el tamaño de stdin pero creo que dado que es una secuencia y no un archivo real, no funciona como sospechaba y siempre está imprimiendo -1. Sé que siempre podría leer el carácter de entrada 1 a la vez mientras != EOF pero me gustaría una solución más genérica para poder terminar con un fd o un ARCHIVO* si existe la entrada estándar para que el resto del programa funcione sin problemas . También me gustaría poder saber su tamaño, pendiente de que el programa anterior haya cerrado la transmisión.

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

avatar de usuario
LatinSuD

Lo estás pensando mal.

Lo que estás tratando de hacer:

Si existe stdin, utilícelo; de lo contrario, verifique si el usuario proporcionó un nombre de archivo.

Lo que deberías estar haciendo en su lugar:

Si el usuario proporciona un nombre de archivo, utilice el nombre de archivo. De lo contrario, use stdin.

No puede saber la duración total de un flujo entrante a menos que lo lea todo y lo mantenga almacenado en el búfer. Simplemente no puedes buscar hacia atrás en las tuberías. Esta es una limitación de cómo funcionan las tuberías. Las tuberías no son adecuadas para todas las tareas y, a veces, se requieren archivos intermedios.

Primero, pídale al programa que le diga qué es lo que está mal comprobando el errnoque se establece en falla, como durante fseek o ftell.

Otros (tonio y LatinSuD) han explicado el error de manejar stdin en lugar de buscar un nombre de archivo. Es decir, primero verifique argc (recuento de argumentos) para ver si hay algún parámetro de línea de comando especificado if (argc > 1)tratando - como un caso especial significado stdin.

Si no se especifican parámetros, suponga que la entrada (va a) provenir de stdinel cual es un Arroyo no presentar, y el fseek la función falla en él.

En el caso de una transmisión, donde no puede usar funciones de biblioteca orientadas a archivos en disco (es decir, fseek y ftell), simplemente tiene que contar la cantidad de bytes leídos (incluidos los caracteres de nueva línea final) hasta recibir fin de semana (fin del documento).

Para usar con archivos grandes, puede acelerarlo usando fgets a una matriz de caracteres para una lectura más eficiente de los bytes en un archivo (de texto). Para un archivo binario necesitas usar fopen(const char* filename, "rb") y use fread en lugar de fgetc/fgets.

También puede comprobar el para feof(stdin) / ferror(stdin) al usar el método de conteo de bytes para detectar cualquier error al leer de una secuencia.

La muestra a continuación debe ser compatible con C99 y portátil.

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

Es posible que desee ver cómo se hace esto en el cat utilidad, por ejemplo.

Ver código aquí. Si no hay un nombre de archivo como argumento, o es “-“, entonces stdin se utiliza para la entrada.
stdin estará allí, incluso si no se le envían datos (pero entonces, su llamada de lectura puede esperar para siempre).

  • Tenga en cuenta que puede utilizar cat fileA - fileB para rodear la entrada estándar con el contenido de fileA y fileB. No se limita a ser el único argumento. Con algunos programas (como paste), puedes usar - varias veces (a veces incluyendo cat, a veces incluso útil). Por ejemplo, paste - - - crea 3 columnas de salida a partir de los datos leídos de la entrada estándar, leyendo una línea para la primera columna, otra para la segunda y una tercera línea para la tercera columna.

    –Jonathan Leffler

    30 de enero de 2019 a las 3:31

avatar de usuario
nos

¿Puede simplemente leer desde stdin a menos que el usuario proporcione un nombre de archivo?

Si no, trate el “nombre de archivo” especial - con el significado de “leer desde stdin”. El usuario tendría que iniciar el programa como cat file | myprogram - si quiere canalizarle datos, y myprogam file si quiere que se lea de un archivo.

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 está en * nix, puede verificar si stdin es un fifo:

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

Aunque eso no manejará al usuario haciendo myprogram <file

También puede verificar si stdin es una terminal/consola

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

Solo probando el final del archivo con feof haría, creo.

  • feof es complicado ya que requiere que haya realizado un intento previo de leer de la secuencia y falló. (Y si hace eso, también podría verificar el motivo de la falla). Tampoco es obvio cómo propone usarlo para esta situación.

    – jamesdlin

    16 de agosto de 2010 a las 18:37

avatar de usuario
Canatella

Tenga en cuenta que lo que desea es saber si stdin está conectado a una terminal o no, no si existe. Siempre existe, pero cuando usa el shell para canalizar algo o leer un archivo, no está conectado a una terminal.

Puede verificar que un descriptor de archivo esté conectado a una terminal a través de las funciones termios.h:

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

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

Esto intentará obtener los atributos de terminal de stdin. Si no está conectado a una tubería, se adjunta a un tty y la llamada a la función tcgetattr tendrá éxito. Para detectar una tubería, verificamos la falla de tcgetattr.

  • feof es complicado ya que requiere que haya realizado un intento previo de leer de la secuencia y falló. (Y si hace eso, también podría verificar el motivo de la falla). Tampoco es obvio cómo propone usarlo para esta situación.

    – jamesdlin

    16 de agosto de 2010 a las 18:37

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad