C语言 从文件或标准输入读取
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3495092/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Read from file or stdin
提问by Ryan
I am writing a utility which accepts either a filename, or reads from stdin.
我正在编写一个接受文件名或从标准输入读取的实用程序。
I would like to know the most robust / fastest way of checking to see if stdin exists (data is being piped to the program) and if so reading that data in. If it doesn't exist, the processing will take place on the filename given. I have tried using the following the test for size of stdinbut I believe since it's a stream and not an actual file, it's not working as I suspected it would and it's always printing -1. I know I could always read the input 1 character at a time while != EOF but I would like a more generic solution so I could end up with either a fd or a FILE* if stdin exists so the rest of the program will function seamlessly. I would also like to be able to know its size, pending the stream has been closed by the previous program.
我想知道检查标准输入是否存在的最健壮/最快的方法(数据正在通过管道传输到程序),如果存在,则读取该数据。如果不存在,则处理将在文件名上进行给。我已经尝试使用以下测试来测试大小,stdin但我相信因为它是一个流而不是一个实际文件,它没有像我怀疑的那样工作并且它总是打印-1. 我知道我总是可以一次读取输入的 1 个字符而 != EOF 但我想要一个更通用的解决方案,所以如果标准输入存在,我最终可以得到 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);
}
Terminal:
终端:
$ echo "hi!" | myprog
Size of stdin: -1
采纳答案by mctylr
First, ask the program to tell you what is wrong by checking the errno, which is set on failure, such as during fseekor ftell.
首先,通过检查errno设置为失败的来让程序告诉您什么错误,例如 duringfseek或ftell。
Others (tonio & LatinSuD) have explained the mistake with handling stdin versus checking for a filename. Namely, first check argc(argument count) to see if there are any command line parameters specified if (argc > 1), treating -as a special case meaning stdin.
其他人(tonio 和 LatinSuD)解释了处理标准输入与检查文件名的错误。即先检查argc(argument count)看是否有指定的命令行参数if (argc > 1),-作为特殊情况处理stdin。
If no parameters are specified, then assume input is (going) to come from stdin, which is a streamnot file, and the fseekfunction fails on it.
如果未指定任何参数,则假设输入(将要)来自stdin,这是一个流而不是文件,并且fseek函数在它上面失败。
In the case of a stream, where you cannot use file-on-disk oriented library functions (i.e. fseekand ftell), you simply have to count the number of bytes read (including trailing newline characters) until receiving EOF(end-of-file).
在流的情况下,您不能使用面向磁盘文件的库函数(即fseek和ftell),您只需计算读取的字节数(包括尾随换行符),直到接收到EOF(文件结尾) .
For usage with large files you could speed it up by using fgetsto a char array for more efficient reading of the bytes in a (text) file. For a binary file you need to use fopen(const char* filename, "rb")and use freadinstead of fgetc/fgets.
对于大文件的使用,您可以通过使用fgets字符数组来加快速度,以便更有效地读取(文本)文件中的字节。对于二进制文件,您需要使用fopen(const char* filename, "rb")和使用fread而不是fgetc/fgets.
You could also check the for feof(stdin)/ ferror(stdin)when using the byte-counting method to detect any errors when reading from a stream.
您还可以在使用字节计数方法时检查 for feof(stdin)/ferror(stdin)以检测从流中读取时的任何错误。
The sample below should be C99 compliant and portable.
下面的示例应符合 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;
}
回答by LatinSuD
You're thinking it wrong.
你想错了。
What you are trying to do:
你想做什么:
If stdin exists use it, else check whether the user supplied a filename.
如果标准输入存在使用它,否则检查用户是否提供了文件名。
What you should be doing instead:
你应该做什么:
If the user supplies a filename, then use the filename. Else use stdin.
如果用户提供文件名,则使用文件名。否则使用标准输入。
You cannot know the total length of an incoming stream unless you read it all and keep it buffered. You just cannot seek backwards into pipes. This is a limitation of how pipes work. Pipes are not suitable for all tasks and sometimes intermediate files are required.
您无法知道传入流的总长度,除非您将其全部读取并保持缓冲状态。你只是不能向后寻找管道。这是管道工作方式的限制。管道并不适合所有任务,有时需要中间文件。
回答by tonio
You may want to look at how this is done in the catutility, for example.
例如,您可能想看看在cat实用程序中这是如何完成的。
See code here.
If there is no filename as argument, or it is "-", then stdinis used for input.
stdinwill be there, even if no data is pushed to it (but then, your read call may wait forever).
请参阅此处的代码。如果没有文件名作为参数,或者它是“-”,则stdin用于输入。
stdin会在那里,即使没有数据被推送到它(但是,你的 read 调用可能会永远等待)。
回答by nos
You can just read from stdin unless the user supply a filename ?
除非用户提供文件名,否则您只能从标准输入读取?
If not, treat the special "filename" -as meaning "read from stdin". The user would have to start the program like cat file | myprogram -if he wants to pipe data to it, and myprogam fileif he wants it to read from a file.
如果不是,则将特殊的“文件名”-视为“从标准输入读取”的意思。用户必须启动程序,就像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
}
If you're on *nix, you can check whether stdin is a fifo:
如果你在 *nix 上,你可以检查 stdin 是否是一个先进先出:
struct stat st_info;
if(fstat(0,&st_info) != 0)
//error
}
if(S_ISFIFO(st_info.st_mode)) {
//stdin is a pipe
}
Though that won't handle the user doing myprogram <file
虽然这不会处理用户做 myprogram <file
You can also check if stdin is a terminal/console
您还可以检查 stdin 是否为终端/控制台
if(isatty(0)) {
//stdin is a terminal
}
回答by Jens Gustedt
Just testing for end of file with feofwould do, I think.
feof我想,只是测试文件结尾就可以了。
回答by Canatella
Note that what you want is to know if stdin is connected to a terminal or not, not if it exists. It always exists but when you use the shell to pipe something into it or read a file, it is not connected to a terminal.
请注意,您想要的是知道 stdin 是否连接到终端,而不是它是否存在。它始终存在,但是当您使用 shell 将某些内容通过管道传输到其中或读取文件时,它并未连接到终端。
You can check that a file descriptor is connected to a terminal via the termios.h functions:
您可以通过 termios.h 函数检查文件描述符是否连接到终端:
#include <termios.h>
#include <stdbool.h>
bool stdin_is_a_pipe(void)
{
struct termios t;
return (tcgetattr(STDIN_FILENO, &t) < 0);
}
This will try to fetch the terminal attributes of stdin. If it is not connected to a pipe, it is attached to a tty and the tcgetattr function call will succeed. In order to detect a pipe, we check for tcgetattr failure.
这将尝试获取 stdin 的终端属性。如果它没有连接到管道,它会附加到一个 tty 并且 tcgetattr 函数调用将成功。为了检测管道,我们检查 tcgetattr 故障。

