我有一个包含 7 个字段的 CSV 文件:
me,val1,val2,val3,val4,val5,val6,
me,val1,val2,val3,val4,val5,val6,
我正在使用以下函数对此进行解析:
void readcsv() {
lk_dispclr();
lk_disptext(2, 0, "Parsing CSV..", 0);
lk_disptext(3, 0, "Please Wait..", 0);
FILE *stream = fopen("input.csv", "r");
if (stream != NULL) {
char line[1024];
while (fgets(line, 1024, stream)) {
char *tmp = strdup(line);
char a1[20] = "";
char b1[20] = "";
char c1[20] = "";
char d1[20] = "";
char e1[20] = "";
char f1[20] = "";
char g1[20] = "";
strcat(a1, getcsvfield(tmp, 1));
strcat(b1, getcsvfield(tmp, 2));
strcat(c1, getcsvfield(tmp, 3));
strcat(d1, getcsvfield(tmp, 4));
strcat(e1, getcsvfield(tmp, 5));
strcat(f1, getcsvfield(tmp, 6));
strcat(g1, getcsvfield(tmp, 7));
//printf("Field 1 would be %s\n", a1);
//printf("Field 2 would be %s\n", getcsvfield(tmp, 2));
//printf("Field 2 would be %s\n", getcsvfield(tmp, 3));
//printf("Field 2 would be %s\n", getcsvfield(tmp, 4));
//printf("Field 2 would be %s\n", getcsvfield(tmp, 5));
//printf("Field 2 would be %s\n", getcsvfield(tmp, 6));
execute("INSERT INTO sdata (uid,sid,name,area,type,stbamount,pkgamount)"
" VALUES('%s','%s','%s','%s','%s','%s','%s');",
a1, b1, c1, d1, e1, f1, g1);
// NOTE strtok clobbers tmp
free(tmp);
}
lk_dispclr();
lk_disptext(2, 4, "CSV Imported!", 1);
lk_getkey();
} else {
lk_dispclr();
lk_disptext(2, 4, "CSV Not Found!", 1);
lk_getkey();
}
}
//Used for parsing CSV
const char *getcsvfield(char *line, int num) {
char buffer[1024] = { 0 };
strcpy(buffer, line);
const char *tok;
for (tok = strtok(buffer, ",");
tok && *tok;
tok = strtok(NULL, ",\n"))
{
if (!--num)
return tok;
}
return NULL;
}
但是如果第 6 个字段 (val5
) 缺失,val6
将被插入表中 val5
的位置,实际上应该是空白的。
我做错了什么?
最佳答案
你的代码有几个问题
主要问题是您在
getcsvfield
中返回一个指向自动存储的指针:您将line
复制到本地数组buffer
并使用strtok
来解析它。当您返回第 n 个元素时,tok
指向buffer
内部,这是一个本地数组。从函数getcsvfield
返回后引用此数组会调用未定义的行为。您可以通过将字段复制到作为getcsvfield
的参数接收的缓冲区中来解决此问题。关于空值,您不能使用
strtok
来解析CSV 格式:它首先会跳过所有出现的定界符,因此您不能将空字段作为序列,
将被解释为单个分隔符。strtok
是一个使用隐藏全局状态的过时函数,您可能也应该避免在其他地方使用它。
这是一个改进的版本:
#include <stdio.h>
#include <string.h>
//Used for parsing CSV
char *getcsvfield(char *dest, int size, const char *line, int num) {
const char *p;
for (p = line; *p != '\0' && *p != '\n';) {
int len = strcspn(p, ",\n"); /* parse field characters */
if (--num <= 0) {
if (len >= size)
len = size - 1;
memcpy(dest, p, len);
dest[len] = '\0';
return dest;
}
p += len;
if (*p == ',')
p++;
}
*dest = '\0';
return NULL;
}
void readcsv(void) {
lk_dispclr();
lk_disptext(2, 0, "Parsing CSV..", 0);
lk_disptext(3, 0, "Please Wait..", 0);
FILE *stream = fopen("input.csv", "r");
if (stream != NULL) {
char line[1024];
while (fgets(line, 1024, stream)) {
char a1[20], b1[20], c1[20], d1[20], e1[20], f1[20], g1[20];
getcsvfield(a1, sizeof a1, line, 1);
getcsvfield(b1, sizeof b1, line, 2);
getcsvfield(c1, sizeof c1, line, 3);
getcsvfield(d1, sizeof d1, line, 4);
getcsvfield(e1, sizeof e1, line, 5);
getcsvfield(f1, sizeof f1, line, 6);
getcsvfield(g1, sizeof g1, line, 7);
execute("INSERT INTO sdata (uid,sid,name,area,type,stbamount,pkgamount)"
" VALUES('%s','%s','%s','%s','%s','%s','%s');",
a1, b1, c1, d1, e1, f1, g1);
}
fclose(stream);
lk_dispclr();
lk_disptext(2, 4, "CSV Imported!", 1);
lk_getkey();
} else {
lk_dispclr();
lk_disptext(2, 4, "CSV Not Found!", 1);
lk_getkey();
}
}
请注意,您的插入方法可能允许攻击者通过 CSV 文件执行 SQL 注入(inject)。在上面的示例中,由于每个字段 20 字节的限制,这会很困难,但在其他地方,您在编写 SQL 命令时应该更加小心。 SQlite 也可能对 execute
参数执行完整性检查。
关于CSV 解析函数无法识别空值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35669438/