csv 从strtok()获取零长度字符串

bzzcjhmw  于 2023-03-05  发布在  其他
关注(0)|答案(5)|浏览(500)

我有一个CSV文件,其中包含以下数据

value;name;test;etc

我尝试使用strtok(string, ";")来拆分它。但是,这个文件可以包含零长度的数据,如下所示:

value;;test;etc

strtok()跳过了哪些数据。有什么方法可以避免strtok像这样跳过零长度数据吗?

cfh9epnr

cfh9epnr1#

一种可能的替代方法是使用BSD函数strsep()代替strtok()(如果可用)。
strsep()函数是strtok()函数的替代函数。由于可移植性的原因,strtok()函数应该是首选的(它符合ISO/IEC 9899:1990(“ISO C90”)),它不能处理空字段,即检测由两个相邻的定界符分隔的字段,或一次用于多个字符串。strsep()函数最早出现在4.4BSD中。
下面是一个简单的示例(也是从该手册页复制的):

char *token, *string, *tofree;

tofree = string = strdup("value;;test;etc");
while ((token = strsep(&string, ";")) != NULL)
    printf("token=%s\n", token);

free(tofree);

输出:

token=value
token=
token=test
token=etc

因此空字段被正确地处理。
当然,正如其他人已经说过的,这些简单的标记器函数都不能正确处理引号内的分隔符,因此如果这是一个问题,您应该使用一个 proper CSV解析库。

r6hnlfcb

r6hnlfcb2#

无法使strtok()不以这种方式运行。从man page
解析字符串中两个或多个连续分隔符字节的序列被视为单个分隔符。字符串开头或结尾的分隔符字节将被忽略。换句话说:strtok()返回的标记总是非空字符串。

但是你可以做的是检查标记之前的'\0'字符数,因为strtok()会将所有遇到的标记替换为'\0',这样你就知道跳过了多少个标记。Source info

标记的结尾自动替换为空字符,标记的开头由函数返回。
和一个代码示例来说明我的意思。

char* aStr = ...;
char* ptr = NULL;

ptr = strtok (...);

char* back = ptr;
int count = -1;
do {
  back--;
  if (back <= aStr) break; // to protect against reads before aStr
  count++;
} while (*back = '\0');

(在没有IDE或测试的情况下编写,可能是无效的实现,但想法成立)。

nsc4cvqm

nsc4cvqm3#

不,你不能。来自"man strtok":
解析字符串中两个或多个连续分隔符的序列被视为单个分隔符。字符串开头或结尾的分隔符将被忽略。换句话说:strtok()返回的标记总是非空字符串。
如果数据在引号或任何其他"转义"中包含分隔符,也可能会遇到问题。
我认为最好的解决方案是获得CSV解析库或编写自己的解析函数。

ma8fv8wu

ma8fv8wu4#

从最近的经验来看,strtok()并不一定要用字符串结尾字符替换所有的分隔符,而是用字符串结尾字符替换它找到的第一个分隔符,并跳过后面的分隔符,但将它们保留在原处。
这意味着在名义上的情况下(分隔符之前没有零长度字符串),在第一次调用strtok()之后,每次调用strtok()都将返回一个指针,指向从\0字符开始的字符串。
如果strtok()读取分隔符之间长度为零的字符串,strtok()将返回一个指针,该指针指向在尚未替换为\0的分隔符字符之后开始的字符串。
下面是我的解决方案,用于确定strtok()是否跳过了分隔符之间的零长度字符串。

// Previous code is needed to point strtok to a string and start ingesting from it.
char * field_string = strtok(NULL, ',');
// Note that this can't be done after the first call to strtok for a given buffer, since the previous character would be outside of the string's memory space.
if (*(field_string-1) == '\0') {
    // no delimiters were skipped
} else {
    // one or more delimiters were skipped
}
sauutmhj

sauutmhj5#

如果strsep()不可用,这是我的解决方案,它建立在@Dariusz的方法上,在我的例子中,我想创建一个指针数组,这些指针指向索引与原始字符串中的字段位置匹配的标记。
另外,我使用逗号而不是分号作为分隔符,代码如下所示:

char *ptr = strtok(line, ",");
char *arr[20]; // Your max may vary.  Checking for overflows is an exercise for the reader
int i = 0;

while (ptr != NULL)
{
    char *back = ptr-2; // go back 2 to check for extra tokens

    // Not all deliminators are converted to nulls, so check for commas too
    while ((*back == '\0' || *back == ',') && (back > line))
    {
      arr[i++] = back; // Some of these may be pointers to comma strings
      back--;
    }
    arr[i++] = ptr;
    
    ptr = strtok(NULL, ",");
}

相关问题