2010-08-16 19 views
21

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

Me gustaría saber la forma más robusta/más rápida de comprobar si stdin existe (los datos están siendo canalizados al programa) y si es así leer esos datos en. Si no existe, el procesamiento tomará colocar en el nombre de archivo dado. He intentado utilizar la siguiente prueba para el tamaño de stdin, pero creo que dado que 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 resto del programa funcione sin problemas . También me gustaría saber su tamaño, a la espera de que el flujo haya sido cerrado por el programa 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 

Respuesta

15

En primer lugar, pedir al programa que le diga lo que está mal marcando la errno, que se establece en caso de fallo, como por ejemplo durante fseek o ftell.

Otros (tonio & LatinSuD) han explicado el error al manejar stdin versus verificar un nombre de archivo. A saber, primero compruebe argc (recuento 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, a continuación, asumir de entrada es (va) que venir de stdin, que es una corrienteno presentar, y la función fseek falla en él.

En el caso de una corriente, donde no se puede utilizar archivos en el disco orientado a funciones de biblioteca (es decir fseek y ftell), simplemente tiene que contar el número de bytes leídos (incluyendo arrastrar caracteres de nueva línea) hasta recibir EOF (fin de archivo).

Para el uso con archivos grandes puede acelerarlo utilizando fgets en una matriz de caracteres 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 en lugar de fgetc/fgets.

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

El siguiente ejemplo 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; 
} 
0

haciendo una prueba para el final del archivo con feof haría, creo.

+1

'feof' es complicado porque requiere que hayas hecho un intento previo de leer de la transmisión y haya fallado. (Y si lo hace, también podría verificar el motivo de la falla.) Tampoco es obvio cómo propone usarlo para esta situación. – jamesdlin

5

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

Ver el código here. Si no hay ningún 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).

4

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

De lo contrario, trate el "nombre de archivo" especial - como "leer de stdin". El usuario debería iniciar el programa como cat file | myprogram - si quiere canalizar datos hacia él, 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ás en * nix, puede comprobar si la entrada estándar es una FIFO:

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

A pesar de que no va a manejar el usuario haciendo myprogram <file

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

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

Lo estás pensando mal.

Lo que estamos tratando de hacer:

Si existe la entrada estándar usarlo, de lo contrario comprobar si el usuario suministra un nombre de archivo.

Lo que debe hacer en su lugar:

Si el usuario proporciona un nombre de archivo, a continuación, utilizar 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 en el búfer. 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.

0

Tenga en cuenta que lo que quiere es saber si la entrada estándar 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.

Usted puede comprobar que un descriptor de fichero está conectado a un 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); 
} 

Este tratará de buscar a los atributos de la terminal de entrada estándar. 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.

+0

tuvo que agregar #include para obtener STDIN_FILENO definido –

Cuestiones relacionadas