写了一个程序,可以格式化的从文件中读出自己想要的字段,然后分别显示在屏幕上,虽然方法可能傻了点,不过还算好用,后来在测试的时候,突然发现了一个问题,不知道为什么,当文件读到最后时,并没有结束,而是将最后一行字串又打了一遍,我试过将fgets换为fwrite,虽然不会再多打出一行,但是因为文件的长度不确定,结果输出的都是乱码,看了看书上的例子,好象只有定义结构体之后格式化输出才能正确读取,难道没有别的办法了么....
__________________________________________________________________
运行环境:
FC6/gcc4.0.1
程序如下:
#include <stdio.h>
#include <string.h>
void file_format_read(void)
{
FILE *filep;
char read_log[100]="\0";
char select_file_name[20]="\0";
char select_file_date[20]="\0";
char select_file_length[20]="\0";
char select_file_size[20]="\0";
filep=fopen("file_format.txt","r");
if(!filep)
printf("open file error!\n");
printf("open file ok!\n");
while(!feof(filep))
{
fgets(read_log,100,filep);
strncpy(select_file_name,read_log,16);
strncpy(select_file_date,read_log+17,16);
strncpy(select_file_length,read_log+34,strcspn(read_log,"#")-strcspn(read_log,"@")-1);
strncpy(select_file_size,strchr(read_log,'#')+1,strcspn(read_log,"$")-strcspn(read_log,"#")-1);
printf("fread = %s\n",read_log);
printf("select_file_name = %s\n",select_file_name);
printf("select_file_date = %s\n",select_file_date);
printf("select_file_length = %s\n",select_file_length);
printf("select_file_size = %s\n",select_file_size);
printf("**********************************\n");
strcpy(select_file_name,"\0");
strcpy(select_file_date,"\0");
strcpy(select_file_length,"\0");
strcpy(select_file_size,"\0");
}
fclose(filep);
}
int main()
{
file_format_read();
return 1;
}
用来读的文件:
名称:file_format.txt
内容:
200706201421.avi 2007-06-20 14:21@58分钟#45M%
200706201422.avi 2007-06-20 14:22@158分钟#45M%
200706201423.avi 2007-06-20 14:23@58分钟#145M%
200706201424.avi 2007-06-20 14:24@58分钟#45M%
200706201425.avi 2007-06-20 14:25@258分钟#245M%
200706201426.avi 2007-06-20 14:26@58分钟#45M%
200706201427.avi 2007-06-20 14:27@58分钟#45M%
200706201428.avi 2007-06-20 14:28@2258分钟#2145M%
200706201429.avi 2007-06-20 14:29@58分钟#45M%
[ 本帖最后由 封神 于 2007-7-3 09:10 编辑 ]
福瑞哈哥 回复于:2007-07-02 20:45:53
记得这种把feof当作循环测试条件,造成最后一行打印两次的问题在论坛已经多次出现了。
一句话:忘掉feof,直接用fgets。
JohnBull 回复于:2007-07-02 20:48:47
可以用feof,但必须记住:当文件刚刚读到文件尾时,feof不会返回true。只有在文件尾部再次进行一次读操作,feof才会返回真。
这与底层系统调用相关。因为只有read返回一次0值,进程才能知道文件结束了。
MMMIX 回复于:2007-07-02 21:50:58
引用:原帖由 福瑞哈哥 于 2007-7-2 20:45 发表
记得这种把feof当作循环测试条件,造成最后一行打印两次的问题在论坛已经多次出现了。
一句话:忘掉feof,直接用fgets。
呵呵,看到标题我就猜到是 feof 惹的祸,一看果然如此。
flw 回复于:2007-07-02 21:54:44
feof() 就是蛇足,见一次说一次。
MMMIX 回复于:2007-07-02 21:57:04
引用:原帖由 flw 于 2007-7-2 21:54 发表
feof() 就是蛇足,见一次说一次。
果然又是这话 :em06::em06::em06:
封神 回复于:2007-07-02 23:17:12
受教了,那么将判断条件改为
while(fgets(read_log,100,filep))
就可以了么?
家里没有编译器,明天到公司试试.
封神 回复于:2007-07-02 23:19:34
话说回来,既然这个函数有漏洞,为什么新版本的GCC没有修正这个错误呢,反尔将很多有用字符串处理函数去掉了不少.
MMMIX 回复于:2007-07-02 23:26:25
引用:原帖由 封神 于 2007-7-2 23:19 发表
话说回来,既然这个函数有漏洞,为什么新版本的GCC没有修正这个错误呢,反尔将很多有用字符串处理函数去掉了不少.
谁说这个是错误/漏洞?
封神 回复于:2007-07-03 08:38:54
这个不算是漏洞么?
ypxing 回复于:2007-07-03 08:44:26
这个不是漏洞,
只是多少有些容易产生误导
引用:原帖由 封神 于 2007-7-3 08:38 发表
这个不算是漏洞么?
ivhb 回复于:2007-07-03 08:52:29
引用:原帖由 封神 于 2007-7-2 20:25 发表
写了一个程序,可以格式化的从文件中读出自己想要的字段,然后分别显示在屏幕上,虽然方法可能傻了点,不过还算好用,后来在测试的时候,突然发现了一个问题,不知道为什么,当文件读到最后时,并没有结束,而是将最后一行字 ...
while (!feof(fp)) {
buf[0] = 0x00;
fgets(buf, sizeof buf, fp);
if (buf[0] == 0x00)
break; // ferror(fp) 或者 feof(fp)
...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情况
}
当然,还是像他们说的一样,直接fgets放在while 里最好了,没有必要的。
[ 本帖最后由 ivhb 于 2007-7-3 08:53 编辑 ]
封神 回复于:2007-07-03 09:09:08
收到,谢了
福瑞哈哥 回复于:2007-07-03 09:28:15
引用:原帖由 ivhb 于 2007-7-3 08:52 发表
while (!feof(fp)) {
buf[0] = 0x00;
fgets(buf, sizeof buf, fp);
if (buf[0] == 0x00)
break; // ferror(fp) 或者 feof(fp)
...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情 ...
没有测试过你的程序,但是感觉有问题。
MMMIX 回复于:2007-07-03 09:38:49
引用:原帖由 封神 于 2007-7-3 08:38 发表
这个不算是漏洞么?
也许在设计之初算是,但是现在这个已经写入标准,那么就不再是 bug,而是标准了 :mrgreen:
ivhb 回复于:2007-07-03 12:25:41
引用:原帖由 福瑞哈哥 于 2007-7-3 09:28 发表
没有测试过你的程序,但是感觉有问题。
:) 如果是磁盘文件来说,这么做不会有什么问题。
因为磁盘文件,ferror几乎不可能为错。只要你打开成功的话。
对于ferror来说,因为没有规定
fgets失败,buf是否会被复写。因此这个是未定义的。
但是到达文件结束,buf会left unchanged。
因此,这么做几乎是等价的吧。
封神 回复于:2007-07-03 15:06:53
最终选择在while中写fgets,对于我个人来说比较好理解,再次谢谢大家。
引用:原帖由 MMMIX 于 2007-7-3 09:38 发表
也许在设计之初算是,但是现在这个已经写入标准,那么就不再是 bug,而是标准了 :mrgreen:
强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改
思一克 回复于:2007-07-03 16:10:27
LZ,你应该判断fgets()为NULL,不判断而继续使用buf的内容,自己的程序是不对的。
foef(stream)针对的是”流“, 用一个indicator(指示)表明文件尾,而feof判断这个指示是否设置,而不是判断当前SEEK数值是否到尾。
seek值到尾了对于“流”不一定是是文件尾,因为“流”是会不断增长的。比如pipe, sockfd等。
不是BUG,不要想象这么多年了编制库函数的人都愚不可及。
NAME
clearerr, feof, ferror, fileno - check and reset stream status
SYNOPSIS
#include <stdio.h>
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
int fileno(FILE *stream);
DESCRIPTION
The function clearerr clears the end-of-file and error indicators for the stream pointed to by stream.
The function feof tests the end-of-file indicator for the stream pointed to by stream, returning non-zero if it is
set. The end-of-file indicator can only be cleared by the function clearerr.
The function ferror tests the error indicator for the stream pointed to by stream, returning non-zero if it is set.
The error indicator can only be reset by the clearerr function.
The function fileno examines the argument stream and returns its integer descriptor.
For non-locking counterparts, see unlocked_stdio(3).
ERRORS
These functions should not fail and do not set the external variable errno. (However, in case fileno detects that
its argument is not a valid stream, it must return -1 and set errno to EBADF.)
CONFORMING TO
The functions clearerr, feof, and ferror conform to X3.159-1989 (鈥樷€楢NSI C鈥欌€?.
SEE ALSO
open(2), unlocked_stdio(3), stdio(3)
ivhb 回复于:2007-07-03 16:48:44
引用:原帖由 思一克 于 2007-7-3 16:10 发表
LZ,你应该判断fgets()为NULL,不判断而继续使用buf的内容,自己的程序是不对的。
foef(stream)针对的是”流“, 用一个indicator(指示)表明文件尾,而feof判断这个指示是否设置,而不是判断当前SEEK数值是否 ...
一直很欣赏 思一克, 不会放过任何一个细小的细节。
见你说了这么多,忍不住顺便也说一下,对于pipe,sock,本身是不可seek的,这是其一;
另外,如果当前的pipe/sock里没有可读的数据,fgets会一直等待,根本不会返回:),因此
也不会给判断buf的机会:) 除非pipe/sock另外一写入端close或者读到足够的字节,或者被信号打断.
如果你能进入到判定buf的时候,一定是1. 读到足够的字节数,2不足,但是已经到文件尾。
如果buf[0] == 0x00, 那么,一定是到了文件尾:)
考虑到低速设备容易被信号打断,因此,一上来就用了,如果对于磁盘文件 ...
[ 本帖最后由 ivhb 于 2007-7-3 16:58 编辑 ]
思一克 回复于:2007-07-03 17:02:40
可能我用直接用sock, pipe做例子不对。
但可以想,LIB库,用一个指示来判断,应该有其道理的,虽然会引起误导。
根据是什么,就是这么成熟的使用多年的库,有是这么简单的问题,不可能有错误。而且man page中说的也是那样判断eof的指示是否设置,而不是判断目前是否读在最后一个字节。至于指示怎么被设置没有说。
再有,且不说以上的,就是程序中判断错误,也应该用最后那个操作fgets()来判断,否则直接fgets后就利用buf内容是绝对不对的,因为fgets返回错误的情况呢?
引用:原帖由 ivhb 于 2007-7-3 16:48 发表
一直很欣赏 思一克, 不会放过任何一个细小的细节。
见你说了这么多,忍不住顺便也说一下,对于pipe,sock,本身是不可seek的,这是其一;
另外,如果当前的pipe/sock里没有可读的数据,fgets会一直等待, ...
福瑞哈哥 回复于:2007-07-03 17:11:00
引用:原帖由 ivhb 于 2007-7-3 08:52 发表
while (!feof(fp)) {
buf[0] = 0x00;
fgets(buf, sizeof buf, fp);
if (buf[0] == 0x00)
break; // ferror(fp) 或者 feof(fp)
...
}
if (ferror(fp)) { // 对应break出错时候蹦出来的情 ...
是的,你的程序没问题。我上午只看到了if(buf[0]==0x00)这句,没看到你上面buf[0]==0x00这句,觉得这样写程序很没有必要。fgets有返回值直接使用就可以了,逻辑越多别人越不容易理解。
MMMIX 回复于:2007-07-03 19:01:39
引用:原帖由 封神 于 2007-7-3 15:06 发表
最终选择在while中写fgets,对于我个人来说比较好理解,再次谢谢大家。
强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改
为了向后兼容。
whyglinux 回复于:2007-07-03 23:46:05
>> 都是feof惹的祸
>> 这个不算是漏洞么?
是漏洞。只不过是你写的程序中存在的漏洞,并不是 feof 函数的过错。产生漏洞的原因是你还没有掌握 feof 函数的正确使用方法,而不是什么“都是feof惹的祸”。
>> 强权标准啊,根本就不好用,还要强迫大家接受,修正这么一个函数又不会死人,真不知道为什么那些家伙不改
首先要想清楚你想要他们改什么,怎么改?
feof 函数的声明和行为已经是包括 ANSI C 标准在内的很多标准规定的函数了。即使是 C 标准委员会的人想对它进行修改也已经是不可能的事了,况且实际上也无需修改。
================
其实正确使用 feof 函数很容易,只要记住一个原则就是了:“先读文件后用 feof 判断”。你上面使用 feof 函数的程序都没有做到这一点,而是先用 feof 函数判断、然后再读文件,所以是不正确的。可根据上面的这个原则修改你的程序,使之正确。
思一克 回复于:2007-07-04 09:01:37
whyglinux说的准确。漏洞是LZ的程序,而不是feof.
引用:原帖由 whyglinux 于 2007-7-3 23:46 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7001994&ptid=956786]
>> 都是feof惹的祸
>> 这个不算是漏洞么?
是漏洞。只不过是你写的程序中存在的漏洞,并不是 feof 函数的过错。产生漏洞的原因是你还没有掌握 feof 函数的正确使用方法,而不是什么“都是feof惹的祸”。
...
while(1) 回复于:2007-07-04 09:41:48
“先读文件后用 feof 判断”
:funk: 原来如此
福瑞哈哥 回复于:2007-07-04 09:49:18
每次看到有人用feof我都会感到同情!
不管feof有没有用,如果使用fgets而不判断返回值都是错误!
如果判断了fgets的返回值,也就没有必要使用feof了。
引用:原帖由 while(1) 于 2007-7-4 09:41 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002848&ptid=956786]
“先读文件后用 feof 判断”
:funk: 原来如此
ivhb 回复于:2007-07-04 10:26:33
引用:原帖由 while(1) 于 2007-7-4 09:41 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002848&ptid=956786]
“先读文件后用 feof 判断”
:funk: 原来如此
假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?
feof仅仅是个宏,仅仅读取了上次读/写动作发生后置于FILE 结构的一个标志位。
只有进一次的读/写动作才会更新那个标志位。
思一克 回复于:2007-07-04 10:31:36
应该仍然是真。因为INDICATOR的缘故
我没有具体测试。
引用:原帖由 ivhb 于 2007-7-4 10:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003216&ptid=956786]
假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?
feof仅仅是个宏,仅仅读取了上次 ...
while(1) 回复于:2007-07-04 10:58:00
引用:原帖由 福瑞哈哥 于 2007-7-4 09:49 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7002918&ptid=956786]
每次看到有人用feof我都会感到同情!
不管feof有没有用,如果使用fgets而不判断返回值都是错误!
如果判断了fgets的返回值,也就没有必要使用feof了。
fgets返回NULL的情况就表示一定是文件读完了么?
福瑞哈哥 回复于:2007-07-04 11:04:13
难道还有其他选择?
fgets返回null表示这个文件对程序来说是读完了。基本上就是这样,有其他情况99%你也不会处理的。
引用:原帖由 while(1) 于 2007-7-4 10:58 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003404&ptid=956786]
fgets返回NULL的情况就表示一定是文件读完了么?
while(1) 回复于:2007-07-04 11:04:44
引用:原帖由 ivhb 于 2007-7-4 10:26 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003216&ptid=956786]
假定进程A读取文件已经到了文件尾,feof测试为真。
如果此时进程B追加A正在读取的文件,你觉得,如果A不发生进一步的读/写动作,
接着测试feof,你觉得是真还是假?
feof仅仅是个宏,仅仅读取了上次 ...
我的理解是:fgets读到文件最后一行的时候(返回不是NULL) ,feof测试为假,再fgets一次(返回为NULL),再feof测试为真。对否?
你的问题我觉得是 如果在A中用feof是真,在B中用feof是假,对否?
while(1) 回复于:2007-07-04 11:06:51
引用:原帖由 福瑞哈哥 于 2007-7-4 11:04 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003435&ptid=956786]
难道还有其他选择?
fgets返回null表示这个文件对程序来说是读完了。基本上就是这样,有其他情况99%你也不会处理的。
我的意思是fgets出错的情况有可能么?这时候fgets是什么反应?好奇
福瑞哈哥 回复于:2007-07-04 11:08:21
看man page,
gets() and fgets() return s on success, and NULL on error or when end
of file occurs while no characters have been read.
不要将问题考虑的那样复杂
理论上fgets确实有出错的可能,但是谁又会去处理它呢,又如何处理呢?至今阅码无数,尚无见识过处理fgets错误的代码。
引用:原帖由 while(1) 于 2007-7-4 11:06 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003452&ptid=956786]
我的意思是fgets出错的情况有可能么?这时候fgets是什么反应?好奇
[ 本帖最后由 思一克 于 2007-7-5 15:20 编辑 ]
MMMIX 回复于:2007-07-04 11:42:53
引用:原帖由 福瑞哈哥 于 2007-7-4 11:08 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003459&ptid=956786]
理论上fgets确实有出错的可能,但是谁又会去处理它呢,又如何处理呢?至今阅码无数,尚无见识过处理fgets错误的代码。
不会这么夸张吧?
while(fgets(..., stream)){
/* ... */
}
if(ferror(stream)){
/* ... */
}
这种例子一堆一堆的。
[ 本帖最后由 MMMIX 于 2007-7-4 11:44 编辑 ]
清汤挂面 回复于:2007-07-05 11:31:10
每次读之前memset一下buffer
福瑞哈哥 回复于:2007-07-05 11:32:43
引用:原帖由 MMMIX 于 2007-7-4 11:42 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7003684&ptid=956786]
不会这么夸张吧?
while(fgets(..., stream)){
/* ... */
}
if(ferror(stream)){
/* ... */
}
这种例子一堆一堆的。
我是没见过。 if(ferror(stream)) {}里面写什么?
xiaomiao 回复于:2007-07-05 11:46:05
不如用C++的ifstream + while循环
ifstream in("/tmp/test.txt",ios::in);
string temp;
while(getline(in,temp,'\n'))
{
//.....
}
MMMIX 回复于:2007-07-05 12:24:07
引用:原帖由 福瑞哈哥 于 2007-7-5 11:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7009049&ptid=956786]
我是没见过。 if(ferror(stream)) {}里面写什么?
来个完整的例子吧,APUE2 的 Figure 15.11
#include "apue.h"
#include <sys/wait.h>
#define PAGER "${PAGER:-more}" /* environment variable, or default */
int
main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;
if (argc != 2)
err_quit("usage: a.out <pathname>");
if ((fpin = fopen(argv[1], "r")) == NULL)
err_sys("can't open %s", argv[1]);
if ((fpout = popen(PAGER, "w")) == NULL)
err_sys("popen error");
/* copy argv[1] to pager */
while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF)
err_sys("fputs error to pipe");
}
if (ferror(fpin))
err_sys("fgets error");
if (pclose(fpout) == -1)
err_sys("pclose error");
exit(0);
}
ivhb 回复于:2007-07-05 14:27:04
如果要提供一个函数给其他人用,ferror判断就有用了吧。
比如读取informix下unload出来的文件,如果行尾是\表示连接下一行。
如果提供一个函数叫做
static FILE *fp;
static int
init_file_read(char *filenmame)
{
fp = fopen(filename, "r");
}
int
has_next_line(register char *s, char spec)
{
char c, tr = 0;
if (s[0] == 0x00)
return (0);
while (c = *s ++) {
if (tr == 1
&& s[0] == 0x00
&& c == '\n')
return (1);
if (c != spec)
tr = 0;
else
tr = 1 - tr;
}
return (0);
}
int
get_consecutive_lines(char *s, int len)
{
static char spec = '\\';
int lines = 0;
int cur_line_len = 0;
if (fp == NULL)
return (-1);
/* s remains unchanged */
if (len < 2)
return (0);
do {
s += cur_line_len;
len -= cur_line_len;
if (len < 2)
break;
if (fgets(s, len, fp) == NULL)
break;
cur_line_len = strlen(s);
if (s[0] != 0x00
&& s[cur_line_len-1] == '\n')
lines ++;
} while (has_next_line(s, spec));
if (ferror(fp))
return (-1);
return (lines);
}
这样可以约定返回<0就是出错,>= 0 就是实际读取的行数,表示成功。
调用你函数的人就可以根据返回来决定采取什么动作了。
.....
我们不讨论这么封装模块化的优劣,仅仅是个例子来说明一下而已。
[ 本帖最后由 ivhb 于 2007-7-5 14:30 编辑 ]
思一克 回复于:2007-07-05 15:22:24
gets() and fgets() return s on success, and NULL on error or when end
of file occurs while no characters have been read.
fgets错误或EOF都返回NULL
福瑞哈哥 回复于:2007-07-05 15:48:01
引用:原帖由 思一克 于 2007-7-5 15:22 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7010802&ptid=956786]
gets() and fgets() return s on success, and NULL on error or when end
of file occurs while no characters have been read.
fgets错误或EOF都返回NULL
是的,fgets在eof和出错时都会返回NULL,只是一般情况下可以忽略错误。当然,可以更严谨一些。
封神 回复于:2007-07-09 09:45:33
汗,没想到已经讨论到这么高深的地步了,太复杂了,我还是只记住while(fgets(...))就好了
思一克 回复于:2007-07-09 10:36:48
其实也没有什么高深的。就是你判断fgets是否真返回了内容就可以了。
还有,CLIB库已经非常成熟,一旦出莫名其妙的问题,先研究自己程序那里有问题了,而不是怀疑系统BUG。
如果细心看MAN PAGE,一切解释都有。
比如,putenv()的参数问题,我也莫名其妙过,但一看man page,就不再上当了。
引用:原帖由 封神 于 2007-7-9 09:45 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7024443&ptid=956786]
汗,没想到已经讨论到这么高深的地步了,太复杂了,我还是只记住while(fgets(...))就好了
ivhb 回复于:2007-07-09 10:46:17
呵呵,手册页真是好东西,对于可能出现的错误都会提醒你,而不是藏着掖着。
封神 回复于:2007-07-10 10:22:35
恩恩,收到了.谢谢大家的指点.
PS:因为E文比较差,看E文资料十分痛苦,哎,什么时候汉语成为世界通用语就好了(无聊的YY中).........
|