Я пишу утилиту, которая принимает либо имя файла, либо читает из stdin.
Я хотел бы знать, какой самый надежный / быстрый способ проверить, существует ли stdin (данные передаются в программу) и, если это так, считывает эти данные. Если это не так t существует, обработка будет происходить по заданному имени файла. Я попытался использовать следующий тест для размера stdin
, но я считаю, что поскольку это поток, а не фактический файл, он не работает, как я подозревал, и всегда печатает -1
. Я знаю, что всегда мог прочитать вводимые 1 символ за раз, в то время как! = EOF, но я бы хотел более общее решение, чтобы я мог получить либо fd, либо FILE *, если stdin существует, поэтому остальная часть программы будет работать без проблем. , Я также хотел бы иметь возможность узнать его размер, пока поток не был закрыт предыдущей программой.
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);
}
Терминал:
$ echo "hi!" | myprog
Size of stdin: -1
Во-первых, попросите программу сказать вам, что не так, проверив errno
, который устанавливается в случае сбоя, например, во время fseek
или ftell
.
Другие (tonio и LatinSuD) объяснили ошибку с обработкой stdin по сравнению с проверкой имени файла. А именно, сначала проверьте argc
(количество аргументов), чтобы узнать, указаны ли какие-либо параметры командной строки if (argc> 1)
, рассматривая -
как особый случай. означает стандартный ввод
.
Если параметры не указаны, предположим, что ввод будет (будет) поступать из stdin
, который является потоком , а не файлом, и функцией fseek
. терпит неудачу.
В случае потока, где вы не можете использовать библиотечные функции, ориентированные на файлы на диске (например, fseek
и ftell
), вам просто нужно подсчитать количество байтов. читать (включая завершающие символы новой строки) до получения EOF (конец файла).
Для использования с большими файлами вы можете ускорить его, используя fgets
в массиве символов для более эффективного чтения байтов в (текстовом) файле. Для двоичного файла вам нужно использовать fopen (const char * filename, "rb")
и использовать fread
вместо fgetc / fgets
.
Вы также можете проверить feof (stdin)
/ ferror (stdin)
при использовании метода подсчета байтов для обнаружения любых ошибок при чтении из потока.
Приведенный ниже образец должен соответствовать стандарту C99 и быть портативным.
#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;
}
Думаю, подойдет просто проверка конца файла с помощью feof
.
Вы ошибаетесь.
Что вы пытаетесь сделать:
Если stdin существует, используйте его, иначе проверьте, указал ли пользователь имя файла.
Что вы должны делать вместо этого:
Если пользователь предоставляет имя файла, используйте имя файла. В противном случае используйте stdin.
Вы не можете узнать общую длину входящего потока, если не прочитаете его все и не сохраните в буфере. Вы просто не можете искать назад в трубы. Это ограничение того, как работают трубы. Трубы подходят не для всех задач, иногда требуются промежуточные файлы.
Вы можете посмотреть, как это делается, например, в утилите cat
.
См. Код здесь .
Если в качестве аргумента нет имени файла или указано «-», то для ввода используется stdin
.
stdin
будет там, даже если в него не отправляются никакие данные (но тогда ваш вызов чтения может ждать вечно).
Вы можете просто читать из stdin, если пользователь не предоставит имя файла?
Если нет, то рассматривайте специальное "filename" -
как означающее "читать из stdin". Пользователь должен будет запустить программу, например cat file | myprogram -
если он хочет передать ей данные, и myprogam 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
}
Если вы находитесь на *nix, вы можете проверить, является ли stdin fifo:
struct stat st_info;
if(fstat(0,&st_info) != 0)
//error
}
if(S_ISFIFO(st_info.st_mode)) {
//stdin is a pipe
}
Хотя это не поможет пользователю выполнить myprogram
Вы также можете проверить, является ли stdin терминалом/консолью
if(isatty(0)) {
//stdin is a terminal
}