我正在编写一个实用程序,它接受文件名或从stdin读取。
我想知道检查stdin是否存在的最强大/最快的方法(数据是否通过管道传输到程序),如果是,则读取该数据。如果不存在,则将进行处理在给出的文件名。我已经尝试使用以下测试stdin
的大小,但我相信因为它是一个流而不是一个实际文件,它不能正常工作,因为我怀疑它会一直打印-1
。我知道我总是可以一次读取输入1个字符!= EOF但是我想要一个更通用的解决方案,所以如果stdin存在,我最终会得到fd或FILE *所以程序的其余部分将无缝运行。我也希望能够知道它的大小,等待前一个程序关闭了流。
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
答案 0 :(得分:22)
你认为这是错误的。
您要做的是:
如果stdin存在,请使用它,否则检查用户是否提供了文件名。
你应该做什么:
如果用户提供文件名,则使用文件名。否则使用标准输入。
除非您全部阅读并保持缓冲,否则您无法知道传入流的总长度。你只是不能向后寻找管道。这是管道工作方式的限制。管道不适合所有任务,有时需要中间文件。
答案 1 :(得分:15)
首先,请检查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
。
在使用字节计数方法检测从流中读取时的任何错误时,您还可以检查for 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 :(得分:5)
例如,您可能希望了解如何在cat
实用程序中完成此操作。
请参阅代码here。
如果没有文件名作为参数,或者它是“ - ”,则stdin
用于输入。
stdin
将在那里,即使没有数据被推送到它(但是,你的读取呼叫可能永远等待)。
答案 3 :(得分:4)
除非用户提供文件名,否则您只能从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
}
答案 4 :(得分:0)
我认为只需使用feof
测试文件结尾即可。
答案 5 :(得分:0)
请注意,您想要知道stdin是否连接到终端,而不是它是否存在。它总是存在但是当你使用shell将某些东西输入或读取文件时,它没有连接到终端。
您可以通过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故障。