一、需求
- 在下面的excel表格中,计算每一个人的总分及评价。当总分大于270时评价为A,当总分位于[240,270]之间时评价为B,当总分低于240分时评价为C。
- 表格名为score.csv(保存为csv是为了将内容以逗号隔开)
语文 数学 英语 总分 评价 张三 90 91 92 李四 80 81 82 王五 70 71 72
二、解决方案
1.命令行输入指令:./xxxx score.csv result.csv。其中xxxx为可执行文件,score.csv作为原数据文件,result.csv作为处理后的输出文件。当result.csv不存在时则创建,存在时截断以防数据覆盖不全。
2.逐行读取,每读取一行原数据文件内容就存入缓冲区数组中,再进行处理,处理后存入输出文件中。3.read_line为读取一行的函数,逐个读取1个字符,若遇到'\r'或'\n'则说明该行结束。
4.采用sscanf函数在指定格式中提取关键数据,
%[^,]
可从当前字符串位置开始读取所有不是逗号的字符,直到遇到逗号或字符串的结尾。5.使用sprintf可以将格式化数据写入一个字符串中。
三、代码实现
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
/*返回值:
*-1:表示错吴或者读到尾部
*i:表示读到的数据个数
*/
int read_line(int fd, unsigned char *buf)
{
int len = 0;
unsigned char c;
int i = 0;
int wei = 0;//尾部判断
while (1)
{
len = read(fd, &c, 1);
if (len < 0)
{
perror("read_line");
return -1;
}
else if (len == 0) // 尾部或者回车后的换行,换行读取的数据是0
{
wei = -1;
break;
}
else
{
if (c != '\n' && c != '\r')
{
buf[i] = c;
i++;
wei = 0;
}
else
{
wei = 0;
break; // 行末跳出
}
}
}
buf[i] = '\0';
if(wei == -1 && i== 0)
return -1;
else
return i;
}
void process_data(unsigned char *data_buf, unsigned char *result_buf)
{
if (data_buf[0] == 0xef) /* 对于UTF-8编码的文件,它的前3个字符是0xef 0xbb 0xbf */
{
strcpy(result_buf, data_buf);
strcat(result_buf,"\r\n"); //在尾部加入回车换行
}
else
{
char name[100];
int score[3];
int sum = 0;
int level;
char *levels[] = {"A", "B", "C"};
sscanf(data_buf, "%[^,],%d,%d,%d", name, &score[0], &score[1], &score[2]);
//printf("result: %s --->get name---> %s\n\r", data_buf, name);
sum = score[0] + score[1] + score[2];
if (sum > 270)
{
level = 0;
}
else if ( sum >= 240 && sum <=270)
{
level = 1;
}
else
level = 2;
sprintf(result_buf,"%s,%d,%d,%d,%d,%s\r\n",name, score[0], score[1], score[2],sum,levels[level]);
}
//printf("%s",result_buf);
}
/* argc=3
* argv[0]=./process_excel
* argv[1]=data.csv
* argv[2]=result.csv
*/
int main(int argc, char **argv)
{
int fd_data;
int fd_result;
unsigned char data_buf[1000];
unsigned char result_buf[1000];
if (argc != 3)
{
printf("Usage: %s <data_csv> <result_csv> \n", argv[0]);
return -1;
}
// 打开原始文件
fd_data = open(argv[1], O_RDONLY);
if (fd_data < 0)
{
perror("open");
return -1;
}
else
{
printf("fd = %d\n", fd_data);
}
// 创建结果文件
fd_result = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR,0664);
if (fd_result < 0)
{
perror("creat");
return -1;
}
else
{
printf("fd = %d\n", fd_result);
}
int len;
while (1)
{
len = read_line(fd_data, data_buf);
if (len == -1)
break;
if (len != 0) //读到回车换行,len=0,result_buf还是上一次的内容
{
//printf("%s\n", data_buf);
process_data(data_buf, result_buf);
write(fd_result, result_buf, strlen(result_buf));
}
}
close(fd_data);
close(fd_result);
}
四、%[^,]
#include <stdio.h>
char data_buf[] = ",Alice,lina,85,90,78";
char name1[50], name2[50];
int score[3];
int main() {
// 使用 sscanf 解析数据
sscanf(data_buf, ",%[^,],%[^,],%d,%d,%d", name1, name2, &score[0], &score[1], &score[2]);
// 输出结果
printf("%s,%s,%d,%d,%d\n", name1, name2, score[0], score[1], score[2]);
return 0;
}
// 此时,name1 的值为 "Alice"
// name2 的值为 "lina"
// score[0] 的值为 85
// score[1] 的值为 90
// score[2] 的值为 78
//输出:Alice,lina,85,90,78
五、read_line函数
写法一:
因为行末\r\n,读到\n再退出,\r也写入buf
int read_line(int fd, char *buf)
{
int len = 0;
char c;
int i = 0;
while(1)
{
len = read(fd, &c, 1);
if(len <= 0)
return -1;
else
{
if(c != '\n')
{
buf[i] = c;
i++;
}
else
{
break;
}
}
}
buf[i] = '\0';
return i;
}
写法二:
读到\r,用lseek函数跳过\n字符
int read_line(int fd, char *buf)
{
int len = 0;
char c;
int i = 0;
while(1)
{
len = read(fd, &c, 1);
if(len <= 0)
return -1;
else
{
if(c != '\r' )
{
buf[i] = c;
i++;
}
else
{
lseek(fd, 1, SEEK_CUR);
break;
}
}
}
buf[i] = '\0';
return i;
}
写法三:
考虑是unix还是windows系统
\r
就是"回到行首",\n
就是"到下一行"
即:\r
是回车,使光标到行首,\n
是换行,使光标下移一格。
通常用的Enter
是两个加起来的,即\r\n
- Unix系统里,每行结尾只有“<换行>”,即“
\n
”;- Windows系统里面,每行结尾是“<换行><回车>”,即“
\n\r
”;- Mac系统里,每行结尾是“<回车>”。
printf aaaa \r\n bbbbbb
print ccccc \n ddddddd
print eeeeeeeee \r ffffff
运行结果
aaaa
bbbbbb
ccccc
ddddddd
ffffff
\r
就是return 回到本行行首,这就会把这一之前的输出覆盖掉,因此ffffff会覆盖eeeeeeeee
而\n
是换行+回车,把光标先移到下一行,然后光标回到下一行的行首
代码具体实现 :
int read_line(int fd, char *buf)
{
int len = 0;
char c;
int i = 0;
while (1)
{
len = read(fd, &c, 1);
if (len <= 0)
return -1;
else
{
if (c == '\r')
{
// 读取到 '\r',检查下一个字符是否是 '\n'
len = read(fd, &c, 1);
if (len > 0 && c != '\n')
{
lseek(fd, -1, SEEK_CUR); // 如果不是 '\n',回退一个字节
}
break; // 行结束
}
else if (c == '\n')
{
// 读取到 '\n',行结束
break;
}
buf[i] = c;
i++;
}
}
buf[i] = '\0';
return i;
}
这个逻辑确保了无论是 Windows 风格的换行符(\r\n
)还是 Unix 风格的换行符(\n
),都能被正确处理。