使用 fgets() 和跳过行从另一个 CSV 写入 CSV
Writing to CSV from another CSV with fgets() and skipping lines
完全披露:学校作业
我一直在编写一些代码以从 CSV 中提取数据并将其移动到另一个 CSV,但是我一直遇到这个我似乎无法克服的错误。
对于我正在处理的部分,用户提供了带有 'DELETE OPT1' 的命令行参数,其中 OPT1 是 CSV 中条目的 ID。 deleteStuff() 函数应该遍历 database.csv 并删除匹配 ID 的第一个 entry/row。它应该通过创建 database.tmp,然后将 database.csv 复制到 tmp,排除第一个匹配条目来实现这一点。然后删除 csv 然后将 tmp 重命名为 database.csv,就好像什么都没发生一样。
但是,源代码 (database.csv) 似乎做错了什么,删除了除 ID 之外的所有内容。下面,我发布了源代码和起始 database.csv,以及 运行 DELETE 10 之后的代码输出。任何帮助将不胜感激,尤其是在理解如何fgets().
请注意,noSpaces() 函数只会删除所有空格,因为根据我们的教授,它们可能包含在输入中。
database.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char show[] = "SHOW";
char delete[] = "DELETE";
char add[] = "ADD";
void noSpaces(char* s) {
const char* d = s;
do {
while (*d == ' ') {
++d;
}
} while (*s++ = *d++);
}
void showStuff() {
FILE* csv = fopen("database.csv", "rt");
if (csv == NULL) {
printf("\n File opening failed");
exit(1);
}
char buffer[800];
char *Gptr, *ID, *name, *cAge, *cGPA;
int counter = 1;
while (fgets(buffer, sizeof(buffer), csv)) {
ID = strtok(buffer, ",");
noSpaces(ID);
name = (strtok(NULL, ","));
noSpaces(name);
cAge = (strtok(NULL, ","));
noSpaces(cAge);
int age = atoi(cAge);
cGPA = (strtok(NULL, ","));
noSpaces(cGPA);
double GPA = strtod(cGPA, &Gptr);
printf("Record %d: ID=%-5s NAME:%-5s AGE:%-5d GPA:%.1f\n", counter, ID, name, age, GPA);
counter++;
}
fclose(csv);
}
void deleteStuff(char givenID[]) {
FILE* csvread = fopen("database.csv", "rt");
if (csvread == NULL) {
printf("\n File opening failed");
exit(1);
}
FILE* csvwrite = fopen("database.tmp", "wt");
char buffer[800];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
ID = strtok(buffer, ",");
noSpaces(ID);
if ((strcmp(ID, givenID) == 0) && (oneAndDone == 0)) {
oneAndDone++;
continue;
}
fprintf(csvwrite, "%s", buffer);
}
system("rm database.csv");
system("mv database.tmp database.csv");
fclose(csvread);
fclose(csvwrite);
if (oneAndDone == 0) {
printf("Sorry, the user was not found. Nothing was deleted.\n\n");
exit(1);
}
}
void addStuff(char gID[], char gName[], char gAge[], char gGPA[]) {
char* Gptr;
int age = atoi(gAge);
double GPA = strtod(gGPA, &Gptr);
FILE* csvappend = fopen("database.csv", "at");
if (csvappend == NULL) {
printf("\n File opening failed");
exit(1);
}
fprintf(csvappend, "%s,%s,%d,%.1f", gID, gName, age, GPA);
fclose(csvappend);
}
void main(int argc, char* argv[]) {
if (argc == 1) {
printf("Your did not provide any arguments. Please enter: ./database CMD OPT1 OPT2 OPT3 OPT4 \n\n");
exit(1);
}
if (strcmp(argv[1], show) == 0) showStuff();
else if (strcmp(argv[1], delete) == 0) {
if (argc <= 2) {
printf("Name of record to delete is missing\n\n");
exit(1);
}
deleteStuff(argv[2]);
}
else if (strcmp(argv[1], add) == 0) {
if (argc <= 5) {
printf("Missing ID, Name, AGE, and GPA Arguments\n\n");
exit(1);
}
addStuff(argv[2], argv[3], argv[4], argv[5]);
}
else printf("The command you requested in invalid. Please select from one of these: SHOW, DELETE, ADD\n\n");
}
原文database.csv:
10,bob,18, 3.5
15,mary,20,4.0
5,tom, 17, 3.8
database.csv之后:
155
使用 strtok
时,您必须了解它会修改您正在标记化的字符串。如果您需要在调用 strtok
后使用该字符串,您应该先复制该字符串。如果您不确定它是如何工作的,请务必检查您正在使用的任何功能的 man page
,例如man 3 strtok
Be cautious when using these functions. If you do use them, note
that:
* These functions modify their first argument.
除非您正在为没有操作系统的其他设备("freestanding" 系统)的微控制器编程,否则 void main()
的使用是错误的。参见:C11 Standard - §5.1.2.2.1 Program startup(p1). See also: What should main() return in C and C++?
您使用标志 int oneAndDone = 0;
-- 这很好。但是,在找到命令行提供的 ID
并递增 oneAndDone++;
(或仅设置 oneAndDone = 1;
)之后,就不再需要调用 strtok
.在 oneAndDone
不再是 0
之后完全跳过 strtok
调用不是更有意义吗?类似于:
char buffer[800];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
if (oneAndDone == 0) {
char bcopy[800];
strcpy (bcopy, buffer);
ID = strtok(bcopy, ",");
noSpaces(ID);
if (strcmp(ID, givenID) == 0) {
oneAndDone = 1;
continue;
}
}
fprintf(csvwrite, "%s", buffer);
}
你做得很好没有跳过缓冲区大小 char buffer[800];
-- 但不要使用 Magic-Numbers 在你的代码中。更好:
...
#include <string.h>
#define MAXC 800 /* if you need a constant, #define one (or more) */
...
char buffer[MAXC];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
if (oneAndDone == 0) {
char bcopy[MAXC];
...
在对 rm
和 mv
进行 system
调用之前,您当然希望关闭正在读取和写入的文件。例如:
fclose(csvread);
fclose(csvwrite);
system("rm database.csv");
system("mv database.tmp database.csv");
您总是希望 检查 return fclose
写入后以捕获任何流错误或文件可能无法捕获的问题只检查写入本身,例如
if (fclose(csvwrite) == EOF)
perror ("fclose-csvwrite");
这样您至少可以了解流是否已成功刷新并且关闭时没有错误。
最后,启用编译器警告 并且在编译没有警告之前不要接受代码。对于 gcc/clang,至少使用 -Wall -Wextra -pedantic
(也可以考虑添加 -Wshadow
)。对于 VS 使用 /W3
。对于任何其他编译器,请查看文档并启用类似的警告。那将指向您在周围添加括号:
} while ((*s++ = *d++));
在 void noSpaces(char* s)
.
这应该让你继续。仔细考虑并进行更改,如果您还有其他问题,请告诉我。
完全披露:学校作业
我一直在编写一些代码以从 CSV 中提取数据并将其移动到另一个 CSV,但是我一直遇到这个我似乎无法克服的错误。
对于我正在处理的部分,用户提供了带有 'DELETE OPT1' 的命令行参数,其中 OPT1 是 CSV 中条目的 ID。 deleteStuff() 函数应该遍历 database.csv 并删除匹配 ID 的第一个 entry/row。它应该通过创建 database.tmp,然后将 database.csv 复制到 tmp,排除第一个匹配条目来实现这一点。然后删除 csv 然后将 tmp 重命名为 database.csv,就好像什么都没发生一样。
但是,源代码 (database.csv) 似乎做错了什么,删除了除 ID 之外的所有内容。下面,我发布了源代码和起始 database.csv,以及 运行 DELETE 10 之后的代码输出。任何帮助将不胜感激,尤其是在理解如何fgets().
请注意,noSpaces() 函数只会删除所有空格,因为根据我们的教授,它们可能包含在输入中。
database.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char show[] = "SHOW";
char delete[] = "DELETE";
char add[] = "ADD";
void noSpaces(char* s) {
const char* d = s;
do {
while (*d == ' ') {
++d;
}
} while (*s++ = *d++);
}
void showStuff() {
FILE* csv = fopen("database.csv", "rt");
if (csv == NULL) {
printf("\n File opening failed");
exit(1);
}
char buffer[800];
char *Gptr, *ID, *name, *cAge, *cGPA;
int counter = 1;
while (fgets(buffer, sizeof(buffer), csv)) {
ID = strtok(buffer, ",");
noSpaces(ID);
name = (strtok(NULL, ","));
noSpaces(name);
cAge = (strtok(NULL, ","));
noSpaces(cAge);
int age = atoi(cAge);
cGPA = (strtok(NULL, ","));
noSpaces(cGPA);
double GPA = strtod(cGPA, &Gptr);
printf("Record %d: ID=%-5s NAME:%-5s AGE:%-5d GPA:%.1f\n", counter, ID, name, age, GPA);
counter++;
}
fclose(csv);
}
void deleteStuff(char givenID[]) {
FILE* csvread = fopen("database.csv", "rt");
if (csvread == NULL) {
printf("\n File opening failed");
exit(1);
}
FILE* csvwrite = fopen("database.tmp", "wt");
char buffer[800];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
ID = strtok(buffer, ",");
noSpaces(ID);
if ((strcmp(ID, givenID) == 0) && (oneAndDone == 0)) {
oneAndDone++;
continue;
}
fprintf(csvwrite, "%s", buffer);
}
system("rm database.csv");
system("mv database.tmp database.csv");
fclose(csvread);
fclose(csvwrite);
if (oneAndDone == 0) {
printf("Sorry, the user was not found. Nothing was deleted.\n\n");
exit(1);
}
}
void addStuff(char gID[], char gName[], char gAge[], char gGPA[]) {
char* Gptr;
int age = atoi(gAge);
double GPA = strtod(gGPA, &Gptr);
FILE* csvappend = fopen("database.csv", "at");
if (csvappend == NULL) {
printf("\n File opening failed");
exit(1);
}
fprintf(csvappend, "%s,%s,%d,%.1f", gID, gName, age, GPA);
fclose(csvappend);
}
void main(int argc, char* argv[]) {
if (argc == 1) {
printf("Your did not provide any arguments. Please enter: ./database CMD OPT1 OPT2 OPT3 OPT4 \n\n");
exit(1);
}
if (strcmp(argv[1], show) == 0) showStuff();
else if (strcmp(argv[1], delete) == 0) {
if (argc <= 2) {
printf("Name of record to delete is missing\n\n");
exit(1);
}
deleteStuff(argv[2]);
}
else if (strcmp(argv[1], add) == 0) {
if (argc <= 5) {
printf("Missing ID, Name, AGE, and GPA Arguments\n\n");
exit(1);
}
addStuff(argv[2], argv[3], argv[4], argv[5]);
}
else printf("The command you requested in invalid. Please select from one of these: SHOW, DELETE, ADD\n\n");
}
原文database.csv:
10,bob,18, 3.5
15,mary,20,4.0
5,tom, 17, 3.8
database.csv之后:
155
使用 strtok
时,您必须了解它会修改您正在标记化的字符串。如果您需要在调用 strtok
后使用该字符串,您应该先复制该字符串。如果您不确定它是如何工作的,请务必检查您正在使用的任何功能的 man page
,例如man 3 strtok
Be cautious when using these functions. If you do use them, note that: * These functions modify their first argument.
除非您正在为没有操作系统的其他设备("freestanding" 系统)的微控制器编程,否则 void main()
的使用是错误的。参见:C11 Standard - §5.1.2.2.1 Program startup(p1). See also: What should main() return in C and C++?
您使用标志 int oneAndDone = 0;
-- 这很好。但是,在找到命令行提供的 ID
并递增 oneAndDone++;
(或仅设置 oneAndDone = 1;
)之后,就不再需要调用 strtok
.在 oneAndDone
不再是 0
之后完全跳过 strtok
调用不是更有意义吗?类似于:
char buffer[800];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
if (oneAndDone == 0) {
char bcopy[800];
strcpy (bcopy, buffer);
ID = strtok(bcopy, ",");
noSpaces(ID);
if (strcmp(ID, givenID) == 0) {
oneAndDone = 1;
continue;
}
}
fprintf(csvwrite, "%s", buffer);
}
你做得很好没有跳过缓冲区大小 char buffer[800];
-- 但不要使用 Magic-Numbers 在你的代码中。更好:
...
#include <string.h>
#define MAXC 800 /* if you need a constant, #define one (or more) */
...
char buffer[MAXC];
char* ID;
int oneAndDone = 0;
while (fgets(buffer,sizeof(buffer),csvread)) {
if (oneAndDone == 0) {
char bcopy[MAXC];
...
在对 rm
和 mv
进行 system
调用之前,您当然希望关闭正在读取和写入的文件。例如:
fclose(csvread);
fclose(csvwrite);
system("rm database.csv");
system("mv database.tmp database.csv");
您总是希望 检查 return fclose
写入后以捕获任何流错误或文件可能无法捕获的问题只检查写入本身,例如
if (fclose(csvwrite) == EOF)
perror ("fclose-csvwrite");
这样您至少可以了解流是否已成功刷新并且关闭时没有错误。
最后,启用编译器警告 并且在编译没有警告之前不要接受代码。对于 gcc/clang,至少使用 -Wall -Wextra -pedantic
(也可以考虑添加 -Wshadow
)。对于 VS 使用 /W3
。对于任何其他编译器,请查看文档并启用类似的警告。那将指向您在周围添加括号:
} while ((*s++ = *d++));
在 void noSpaces(char* s)
.
这应该让你继续。仔细考虑并进行更改,如果您还有其他问题,请告诉我。