2010-08-16 3 views
21

Sto scrivendo un'utilità che accetta un nome file o legge da stdin.Lettura da file o stdin

Vorrei sapere il metodo più robusto/più veloce per verificare se esiste stdin (i dati vengono inviati al programma) e in tal caso leggere tali dati. Se non esiste, l'elaborazione richiederà posiziona sul nome file indicato. Ho provato a utilizzare il seguente test per la dimensione di stdin ma credo poiché si tratta di un flusso e non di un file effettivo, non funziona come sospettavo sarebbe e stampa sempre -1. So che potrei sempre leggere il carattere di input 1 alla volta mentre! = EOF ma vorrei una soluzione più generica in modo che potessi finire con un fd o un FILE * se stdin esiste così il resto del programma funzionerà perfettamente . Mi piacerebbe anche essere in grado di conoscere le sue dimensioni, in attesa che il flusso sia stato chiuso dal programma precedente.

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 

risposta

15

Innanzitutto, chiedere al programma di indicare l'errore verificando errno, impostato su errore, ad esempio durante fseek o ftell.

Altri (tonio & LatinSuD) hanno spiegato l'errore con la gestione di stdin rispetto al controllo di un nome file. Vale a dire, per prima cosa controllare argc (argomento conteggio) per vedere se ci sono parametri della riga di comando specificati if (argc > 1), trattare - come un caso speciale che significa stdin.

Se non vengono specificati parametri, quindi assumere ingresso è (andando) provenire da stdin, che è un flusso non depositare, e la funzione fseek esito negativo su di esso.

Nel caso di un ruscello, dove non è possibile utilizzare il file-on-disk orientato funzioni di libreria (ad esempio fseek e ftell), è sufficiente contare il numero di byte letti (compresi i caratteri finali di nuova riga) fino a ricevere EOF (fine del file).

Per l'utilizzo con file di grandi dimensioni è possibile velocizzarlo utilizzando fgets in un array di caratteri per una lettura più efficiente dei byte in un file (testo). Per un file binario è necessario utilizzare fopen(const char* filename, "rb") e utilizzare fread anziché fgetc/fgets.

È inoltre possibile controllare il numero feof(stdin)/ferror(stdin) quando si utilizza il metodo di conteggio dei byte per rilevare eventuali errori durante la lettura da uno streaming.

L'esempio di seguito deve essere conforme C99 e portatile.

#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

solo test per la fine del file con feof avrebbe fatto, credo.

+1

'feof' è difficile in quanto richiede di aver fatto un precedente tentativo di leggere dallo stream e non è riuscito. (E se lo fai, potresti anche controllare il motivo del fallimento.) Non è ovvio come tu lo proponga di usarlo anche per questa situazione. – jamesdlin

5

Si potrebbe voler vedere come questo è fatto nell'utilità cat, per esempio.

Vedere codice here. Se non esiste alcun nome file come argomento, oppure è "-", per l'immissione viene utilizzato stdin. stdin sarà lì, anche se non viene inviato alcun dato (ma la tua chiamata in lettura potrebbe attendere per sempre).

4

È possibile leggere solo da stdin a meno che l'utente non fornisca un nome file?

In caso contrario, considerare lo speciale "nome file" - come "letto da stdin". L'utente dovrebbe avviare il programma come cat file | myprogram - se vuole reindirizzare i dati ad esso, e myprogam file se vuole che legga da un 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 
    } 

Se siete su * nix, è possibile verificare se stdin è una FIFO:

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

Anche se questo non gestirà l'utente facendo myprogram <file

Si può anche verificare se stdin è un terminale/console

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

Stai pensando che sia sbagliato.

Che cosa si sta cercando di fare:

Se stdin esiste usarlo, altrimenti verificare se l'utente ha fornito un nome di file.

Che cosa si dovrebbe fare, invece:

Se l'utente fornisce un nome di file, quindi utilizzare il nome del file. Altrimenti usa lo stdin.

Non è possibile conoscere la lunghezza totale di un flusso in entrata a meno che non si legga tutto e lo si mantenga nel buffer. Non puoi cercare all'indietro nei tubi. Questa è una limitazione di come funzionano i tubi.I tubi non sono adatti a tutte le attività e talvolta sono necessari file intermedi.

0

Si noti che ciò che si vuole è quello di sapere se stdin è collegato ad un terminale o meno, non se esiste. Esiste sempre ma quando usi la shell per infilare qualcosa dentro di esso o leggere un file, non è connesso ad un terminale.

È possibile verificare che un descrittore di file è collegato ad un terminale tramite le funzioni termios.h:

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

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

Questo cercherà di recuperare gli attributi del terminale di stdin. Se non è collegato a una pipe, è collegato a una tty e la chiamata alla funzione tcgetattr avrà esito positivo. Per rilevare una pipe, controlliamo il fallimento di tcgetattr.

+0

ha dovuto aggiungere #include per ottenere definito STDIN_FILENO –