在 C 中使用多字符定界符拆分 char 字符串

split char string with multi-character delimiter in C

我想根据多字符分隔符拆分 char *string。我知道 strtok() 用于拆分字符串,但它适用于单个字符定界符。

我想根据 "abc" 等子字符串或任何其他子字符串拆分 char *string。如何实现?

您可以使用 strstr() 轻松编写自己的解析器来实现相同的目的。基本算法可能是这样的

  • 使用strstr()查找整个分隔符字符串的第一次出现
  • 标记索引
  • 从开始复制到标记的索引,这将是您期望的标记。
  • 为后续条目解析输入,调整初始字符串的层级以按标记长度 + 分隔符字符串的长度推进。

编辑:考虑了 Alan 和 Sourav 的建议并为此编写了基本代码。

#include <stdio.h>

#include <string.h>

int main (void)
{
  char str[] = "This is abc test abc string";

  char* in = str;
  char *delim = "abc";
  char *token;

  do {

    token = strstr(in,delim);

    if (token) 
      *token = '[=10=]';

    printf("%s\n",in);

    in = token+strlen(delim);

  }while(token!=NULL);


  return 0;
}

找到所需序列发生的点非常容易:strstr 支持:

char str[] = "this is abc a big abc input string abc to split up";
char *pos = strstr(str, "abc");

因此,此时 pos 指向较大字符串中 abc 的第一个位置。这就是事情变得有点丑陋的地方。 strtok 有一个令人讨厌的设计,它 1) 修改原始字符串,以及 2) 在内部字符串中存储指向 "current" 位置的指针。

如果我们不介意做大致相同的事情,我们可以这样做:

char *multi_tok(char *input, char *delimiter) {
    static char *string;
    if (input != NULL)
        string = input;

    if (string == NULL)
        return string;

    char *end = strstr(string, delimiter);
    if (end == NULL) {
        char *temp = string;
        string = NULL;
        return temp;
    }

    char *temp = string;

    *end = '[=11=]';
    string = end + strlen(delimiter);
    return temp;
}

这确实有效。例如:

int main() {
    char input [] = "this is abc a big abc input string abc to split up";

    char *token = multi_tok(input, "abc");

    while (token != NULL) {
        printf("%s\n", token);
        token = multi_tok(NULL, "abc");
    }
}

大致产生预期的输出:

this is
 a big
 input string
 to split up

尽管如此,它很笨拙,很难实现线程安全(您必须使其内部 string 变量成为线程局部变量)并且通常只是一个蹩脚的设计。使用(例如)类似 strtok_r 的接口,我们至少可以解决线程安全问题:

typedef char *multi_tok_t;

char *multi_tok(char *input, multi_tok_t *string, char *delimiter) {
    if (input != NULL)
        *string = input;

    if (*string == NULL)
        return *string;

    char *end = strstr(*string, delimiter);
    if (end == NULL) {
        char *temp = *string;
        *string = NULL;
        return temp;
    }

    char *temp = *string;

    *end = '[=14=]';
    *string = end + strlen(delimiter);
    return temp;
}

multi_tok_t init() { return NULL; }

int main() {
    multi_tok_t s=init();

    char input [] = "this is abc a big abc input string abc to split up";

    char *token = multi_tok(input, &s, "abc");

    while (token != NULL) {
        printf("%s\n", token);
        token = multi_tok(NULL, &s, "abc");
    }
}

我想我暂时就此打住——为了获得一个真正干净的界面,我们真的想重新发明协程之类的东西,这对 post 来说可能有点过头了。

我写了一个线程安全的简单实现:

struct split_string {
    int len;
    char** str;
};
typedef struct split_string splitstr;
splitstr* split(char* string, char* delimiter) {
    int targetsize = 0;
    splitstr* ret = malloc(sizeof(splitstr));
    if (ret == NULL)
        return NULL;
    ret->str = NULL;
    ret->len = 0;
    char* pos;
    char* oldpos = string;
    int newsize;
    int dlen = strlen(delimiter);
    do {
        pos = strstr(oldpos, delimiter);
        if (pos) {
            newsize = pos - oldpos;
        } else {
            newsize = strlen(oldpos);
        }
        char* newstr = malloc(sizeof(char) * (newsize + 1));
        strncpy(newstr, oldpos, newsize);
        newstr[newsize] = '[=10=]';
        oldpos = pos + dlen;
        ret->str = realloc(ret->str, (targetsize+1) * sizeof(char*));
        ret->str[targetsize++] = newstr;
        ret->len++;
    } while (pos != NULL);
    return ret;
}

使用:

splitstr* ret = split(contents, "\n");
for (int i = 0; i < ret->len; i++) {
    printf("Element %d: %s\n", i, ret->str[i]);
}

修改后的 strsep 实现支持 multi-bytes 定界符

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/**
 * Split a string into tokens
 * 
 * @in: The string to be searched
 * @delim: The string to search for as a delimiter
 */
char *strsep_m(char **in, const char *delim) {
  char *token = *in;

  if (token == NULL)
    return NULL;
    
  char *end = strstr(token, delim);
  
  if (end) {
    *end = '[=10=]';
    end += strlen(delim);
  }
  
  *in = end;
  return token;
}

int main() {
  char input[] = "a##b##c";
  char delim[] = "##";
  char *token = NULL;
  char *cin = (char*)input;
  while ((token = strsep_m(&cin, delim)) != NULL) {
    printf("%s\n", token);
  }
}