Leer del archivo o stdin

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

Me gustaría saber cuál es la forma más robusta / más rápida de verificar para ver si stdin existe (los datos están siendo canalizados al progtwig) y si es así leer esos datos en. Si no existe, el procesamiento tendrá lugar en el nombre del archivo dado. Intenté usar la siguiente prueba para el tamaño de stdin pero creo que como es una secuencia y no un archivo real, no está funcionando como sospechaba y siempre está imprimiendo -1 . Sé que siempre podría leer el carácter de entrada 1 a la vez cuando! = EOF, pero me gustaría una solución más genérica para poder terminar con un fd o un ARCHIVO * si stdin existe para que el rest del progtwig funcione sin problemas . También me gustaría saber su tamaño, a la espera de que el flujo haya sido cerrado por el progtwig anterior.

 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 

Primero, solicite al progtwig que le diga cuál es el problema al verificar el error, que está configurado en caso de falla, como durante fseek o ftell .

Otros (tonio y LatinSuD) han explicado el error al manejar stdin versus verificar un nombre de archivo. A saber, primero compruebe argc (conteo de argumentos) para ver si hay algún parámetro de línea de comandos especificado if (argc > 1) , tratando - como un caso especial que significa stdin .

Si no se especifican parámetros, entonces suponga que la entrada es (going) provenir de stdin , que es una secuencia no archivo, y la función fseek falla en ella.

En el caso de una transmisión, donde no puede usar las funciones de biblioteca orientadas a archivos en disco (es decir, fseek y ftell ), simplemente debe contar el número de bytes leídos (incluidos los caracteres de nueva línea) hasta recibir EOF (fin de archivo).

Para el uso con archivos de gran tamaño, puede acelerarlo usando fgets en una matriz char para una lectura más eficiente de los bytes en un archivo (de texto). Para un archivo binario necesita usar fopen(const char* filename, "rb") y usar fread lugar de fgetc/fgets .

También puede verificar la función de feof(stdin) / ferror(stdin) cuando usa el método de conteo de bytes para detectar cualquier error al leer de una transmisión.

El ejemplo siguiente debe ser compatible con C99 y portátil.

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

Estás pensando que está mal.

Lo que estás tratando de hacer:

Si stdin existe, úselo, 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, entonces use el nombre del archivo. De lo contrario, use stdin.

No puede conocer la longitud total de una transmisión entrante a menos que la lea todo y la mantenga almacenada. Usted simplemente no puede buscar hacia atrás en 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.

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

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

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

De lo contrario, trate el “nombre de archivo” especial - que significa “leer de stdin”. El usuario debería iniciar el progtwig como cat file | myprogram - cat file | myprogram - si quiere canalizar datos hacia él, y myprogam file si quiere que 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ás en * nix, puedes 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 que está haciendo myprogram

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

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

feof que feof con probar el final del archivo con feof .

Tenga en cuenta que lo que desea saber es si stdin está conectado a un terminal o no, no si existe. Siempre existe, pero cuando usa el intérprete de comandos para canalizar algo en él o leer un archivo, no está conectado a un terminal.

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

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

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