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

7

我有一个包含类似以下内容的CSV文件:

value;name;test;etc

我正在尝试使用strtok(string, ";")来拆分文本。然而,这个文件可能包含零长度的数据,就像这样:

value;;test;etc
< p > strtok() 会跳过长度为零的数据。有没有办法可以避免strtok() 跳过这样的数据?


你的平台上是否有 strsep() 函数?它的用法与 strtok() 非常相似,但可以正确返回空字段。 - Martin R
@MartinR 可能吧。我正在使用带有 Linux 3.10.10 的 Fedora。 - Mauren
1
那可能是一种替代方案。但即使如此,它也无法正确处理引号内的分隔符,例如 aaa;bbb;"ddd;eee";fff - Martin R
@MartinR 幸运的是,我现在不需要这个功能。我会尝试使用 strsep() - Mauren
你使用哪种编程语言?你可以在标题中包含它。 - David Spector
5个回答

9

如果可用,一种可能的替代方案是使用BSD函数strsep()而不是strtok()

man page:

strsep()函数旨在取代strtok()函数。虽然为了可移植性应首选strtok()函数(它符合ISO/IEC 9899:1990("ISO C90")),但无法处理空字段,即检测由两个相邻的分隔符字符分隔的字段或用于一次以上的单个字符串。 strsep()函数最初出现在4.4BSD中。

这里有一个简单的示例(也是从那个man page复制的):

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

因此,空字段被正确处理。

当然,正如其他人已经说过的那样,这些简单的分词函数都不能正确处理引号内部的分隔符,所以如果这是一个问题,您应该使用一个适当的 CSV解析库。


4

无法使strtok()不以这种方式运行。来自man page

在解析字符串中,两个或多个连续的分隔符字节序列被视为单个分隔符。字符串开头或结尾处的分隔符字节将被忽略。换句话说:由strtok()返回的令牌始终是非空字符串。

但您可以检查令牌前的'\0'字符数,因为strtok()会用'\0'替换所有遇到的令牌。这样,您就知道跳过了多少个令牌。来自源信息

令牌的末尾自动替换为空字符,并且函数返回令牌的开头。

下面是一个代码示例,以说明我的意思。

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或测试,可能是无效的实现,但思路是正确的。)

听起来不错。我会尝试这种方法。 - Mauren
1
如果有关于反对票的评论,我将不胜感激。如果这个实现有什么问题,我想进行更正。 - Dariusz
如果strsep()不可用,我喜欢这种方法,但解决方案存在一些问题。首先,正如@CoolNamesAllTaken所提到的那样,strtok()并不一定会将所有分隔符替换为字符串结束字符。因此,您的while循环需要检查分隔符和null。此外,您对null的检查使用了赋值。 - Phil R

2

不行。

解释:"man strtok"中写道:被解析字符串中的两个或多个连续分隔符字符被视为单个分隔符。字符串开头或结尾处的分隔符被忽略。换句话说:strtok()返回的标记始终是非空字符串。

如果您的数据中包含在引号或任何其他“转义”中的分隔符,则可能会遇到问题。

我认为最好的解决方案是获取一个CSV解析库或编写自己的解析函数。


编写自己的解析函数是我迄今为止试图避免的事情。 - Mauren
1
避免这种情况其实是个好主意。在另一个StackOverflow线程中推荐了这个库:http://sourceforge.net/projects/libcsv/ - Benjamin Maurer

0

从最近的经验来看,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
}

2
答案必须回答问题;您不应该将评论作为自己的答案发布在其他答案上。 - Lundin

0
如果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, ",");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接