Чтение из файла или stdin
Я пишу утилиту, которая принимает либо имя файла, либо читает из stdin.
Я хотел бы узнать самый надежный/быстрый способ проверить, существует ли stdin (данные передаются в программу), и если так читать эти данные. Если он не существует, обработка будет иметь место по имени файла. Я попытался использовать следующий тест для размера 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
Ответы
Ответ 1
Сначала попросите программу сообщить вам, что не так, проверив errno
, который установлен на отказ, например, во время fseek
или ftell
.
Другие (tonio и LatinSuD) объяснили ошибку при обработке stdin и проверке имени файла. А именно, сначала проверьте argc
(количество аргументов), чтобы увидеть, есть ли какие-либо параметры командной строки, указанные if (argc > 1)
, рассматривая -
как особый случай, означающий stdin
.
Если параметры не заданы, предположим, что вход (идущий) поступает из stdin
, который является потоком, а не работает fseek
.
В случае потока, где вы не можете использовать функции библиотеки на диске, ориентированные на диск (т.е. fseek
и ftell
), вам просто нужно подсчитать количество прочитанных байтов (включая завершающие символы новой строки) до тех пор, пока получение EOF (конец файла).
Для использования с большими файлами вы можете ускорить его, используя fgets
в массив char для более эффективного чтения байтов в текстовом файле. Для двоичного файла вам нужно использовать 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;
}
Ответ 2
Вы думаете, что это неправильно.
Что вы пытаетесь сделать:
Если stdin существует, используйте его, иначе проверьте, предоставил ли пользователь имя файла.
Что вы должны делать вместо этого:
Если пользователь поставляет имя файла, используйте имя файла. Просто используйте stdin.
Вы не можете узнать общую длину входящего потока, если вы не прочтете его и не буферизуете. Вы просто не можете искать назад в трубы. Это ограничение работы труб. Трубы не подходят для всех задач, и иногда требуются промежуточные файлы.
Ответ 3
Вы можете посмотреть, как это делается в утилите cat
, например.
Смотрите код здесь.
Если в качестве аргумента нет имени файла, или это "-", то для ввода используется stdin
.
stdin
будет там, даже если никакие данные не будут нажаты (но тогда ваш читаемый вызов может ждать всегда).
Ответ 4
Вы можете просто читать из stdin, если пользователь не подал имя файла?
Если нет, обратитесь к специальному "filename" -
как к значению "read from 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 <file
Вы также можете проверить, является ли stdin терминалом/консолью
if(isatty(0)) {
//stdin is a terminal
}
Ответ 5
Простое тестирование для конца файла с feof
сделало бы, я думаю.
Ответ 6
Обратите внимание, что вы хотите знать, подключен ли stdin к терминалу или нет, а не если он существует. Он всегда существует, но когда вы используете оболочку для передачи чего-либо в нее или чтения файла, она не подключается к терминалу.
Вы можете проверить, что файловый дескриптор подключен к терминалу через функции termios.h:
#include <termios.h>
#include <stdbool.h>
bool stdin_is_a_pipe(void)
{
struct termios t;
return (tcgetattr(STDIN_FILENO, &t) < 0);
}
Это попытается получить атрибуты терминала stdin. Если он не подключен к каналу, он подключен к tty, и вызов функции tcgetattr будет успешным. Чтобы обнаружить трубу, мы проверяем отказ tcgetattr.