Leggi da file o stdin

Sto scrivendo un’utilità che accetta un nome file o legge da 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 avverrà sul nome del file dato. Ho provato a utilizzare il seguente test per la dimensione di stdin ma credo poiché si tratta di un stream e non di un file vero e proprio, non funziona come sospetto 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 stream 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); } 

Terminale:

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

Innanzitutto, chiedi al programma di dirti cosa è sbagliato controllando l’ errno , che è impostato in caso di errore, ad esempio durante fseek o ftell .

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

Se non vengono specificati parametri, supponiamo che l’input sia (going) proveniente da stdin , che è un stream non file, e la funzione fseek non riesce su di esso.

Nel caso di uno stream, in cui non è ansible utilizzare le funzioni della libreria orientata al file su disco (es. fseek e ftell ), è sufficiente contare il numero di byte letti (inclusi i caratteri di fine riga finali) fino alla ricezione di EOF ( fine- di- file).

Per l’utilizzo con file di grandi dimensioni è ansible velocizzarlo utilizzando fgets in un array di caratteri per una lettura più efficiente dei byte in un file (testo). Per un file binario devi usare fopen(const char* filename, "rb") e usare fread invece di fgetc/fgets .

È anche ansible controllare feof(stdin) / ferror(stdin) quando si utilizza il metodo di conteggio dei byte per rilevare eventuali errori durante la lettura da un stream.

L’esempio qui sotto dovrebbe essere compatibile con C99 e portatile.

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

Stai pensando che sia sbagliato.

Cosa stai provando a fare:

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

Cosa dovresti fare invece:

Se l’utente fornisce un nome file, utilizzare il nome file. Altrimenti usa lo stdin.

Non puoi conoscere la lunghezza totale di un stream in entrata a meno che non lo legga tutto e lo tenga in memoria. 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.

Ad esempio, potresti voler vedere come è fatto cat del cat .

Vedi il codice qui . Se non c’è alcun nome di file come argomento, o è “-“, quindi stdin viene utilizzato per l’input. stdin sarà lì, anche se non viene inviato alcun dato (ma la tua chiamata in lettura potrebbe attendere per sempre).

Puoi semplicemente leggere da stdin a meno che l’utente non fornisca un nome file?

In caso contrario, trattare il “nome file” speciale - come “letto da stdin”. L’utente dovrebbe avviare il programma come cat file | myprogram - cat file | myprogram - se vuole pipe 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 stai su * nix, puoi verificare se stdin è un 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 che esegue il myprogram

Puoi anche controllare se stdin è un terminale / console

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

feof che fare solo test per la fine del file con feof .

Nota che quello che vuoi è sapere se stdin è connesso a 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.

È ansible verificare che un descrittore di file sia connesso a un terminale tramite le funzioni termios.h:

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

Questo tenterà 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.