如何从C中具有固定模式的字符串中提取子字符串?

时间:2018-11-30 15:44:00

标签: c

有人可以告诉我如何在此字符串中逐个打印每个单词吗? 就像我有一个包含随机单词但具有固定模式的字符串,其中包含要打印的单词。但是,我的代码仅显示第一个单词“ apple”,然后停止。

这是字符串:

Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv

我想打印“苹果”,“橙色”,“最佳水果”,“西瓜”

char * v = b;
char * m;
char * p;

int t = 0;
int i = 0;
while(strstr(v, "am") != NULL){
    m = strstr(v, "am");
    //printf("%s\n",m);

    p = strtok(m, "/");
    p  = strtok(NULL , "/");
    printf("%s\n", p);
    v+=5;
}

6 个答案:

答案 0 :(得分:1)

因为您正在使用strtok,所以只能找到第一个。

strtok通过添加NUL字符来生成单独的标记,从而更改了标记化的字符串,因此,在循环结束时,您的字符串将看起来像"am \0apple\0 rv dbndkbrb am /orange/"。当您将v前进5个字符时,您不会远远超过刚刚处理过的内容,无法到达字符串的其余部分。

使用strtok而不是使用strchr,它会找到指定字符的下一个实例。用它来查找起始斜杠和结尾斜杠,并将结尾的斜杠替换为NUL。然后,您可以使用它(在由p2表示的代码中)将v正确放置在下一个要处理的文本块的开头。

while(strstr(v, "am") != NULL){
    m = strstr(v, "am");
    p = strchr(m, '/'); // Start
    if (!p) {
        v += 2;
        continue;
    }
    p++;
    p2 = strchr(p , '/'); // End
    if (!p2) {
        v = p;
        continue;
    }
    *p2 = '\0';
    printf("%s\n", p);
    v = p2+1;
}

答案 1 :(得分:0)

使用putchar可以像这样:

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

int main(void) {
    char str[128] = "Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv";

    char* p;                  // A help pointer for parsing the string
    p = strstr(str, "am /");  // Find the first match
    while (p)
    {
        p += 4;  // Increment p to point just after "am /"

        // print the word
        putchar('"');
        while (*p && *p != '/') putchar(*p++);  // notice the increment of p
        putchar('"');
        putchar(' ');

        if (p) p = strstr(p, "am /");  // find next match
    }
    return 0;
}

输出:

"apple" "orange" "bestfruit" "watermelon" 

答案 2 :(得分:0)

由于使用strtok,因此您只能找到第一个匹配项。试试:

char string[] = "Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv";

for (char *i = string; i != NULL; i = strstr(i, "am")) {
  char *start;
  char *end;
  char *match;

  if (start = strstr(i, "/")) {
    if (end = strstr(start + 1, "/")) {
        int start_index = start - string;
        int end_index = end - string;
        printf("%.*s\n", end_index - start_index - 1, string + start_index + 1);
    }
  }

  i += 2;
}

答案 3 :(得分:0)

函数strtok()通过在定界符的位置插入'\ 0'来修改原始字符串。 (请阅读strtok的文档。)当字符串被该'\ 0'截断时,下一个strstr将找不到该模式。

这里是原始代码的略微修改版本。

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

int main(void) {
char str[] = "Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv";

char * v = str;
char * m;
char * p;

int t = 0;
int i = 0;
while(strstr(v, "am /") != NULL){
    m = strstr(v, "am");
    //printf("%s\n",m);

    p = strtok(m, "/");
    p  = strtok(NULL , "/");
    if(p != NULL)
    {
        printf("%s\n", p);
        v = p + strlen(p)+1;
    }
    else
    {
        v+=5;
    }
}

return 0;
}

答案 4 :(得分:0)

FWIW,这是一个使用GNU regex库的示例(取决于您的平台,该库可能不可用)。对于您尝试做的事情来说,这可能太过分了,但是它是其他人向您展示的方法的替代方法。根据您要匹配的模式的复杂程度,它可能会派上用场。

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

int main( void ) 
{
    /**
     * Text we want to search.
     */
    const char *text = "Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv";

    /**
     * The following pattern will isolate the strings between the '/'
     * characters.  ".*" matches any sequence of characters, so basically
     * the pattern reads, "skip over any characters until we see an opening
     * '/', then match everything up to the next '/', then repeat 3 more
     * times".  
     */
    const char *ptn = ".*/(.*)/.*/(.*)/.*/(.*)/.*/(.*)/.*";

    /**
     * Regular expression type.
     */
    regex_t regex;

    /**
     * Compile the regular expression
     */
    if ( regcomp(&regex, ptn, REG_EXTENDED) != 0 )
    {
        fprintf( stderr, "regcomp failed on %s\n", ptn );
        exit( 0 );
    }

    /**
     * Set up an array to store the start and end positions of the
     * matched substrings within text.  
     */
    fprintf( stdout, "Number of subexpressions: %zu\n", regex.re_nsub );
    size_t matchCount = regex.re_nsub + 1;
    regmatch_t pmatch[matchCount];

    int ret;
    /**
     * Execute the regular expression, then print out the matched expressions
     */
    if ( ( ret = regexec( &regex, text, matchCount, pmatch, 0)) != 0 )
    {
        fprintf( stderr, "%s does not match %s, return code %d\n", text, ptn, ret );
    }
    else
    {
        fprintf( stdout, "Sucessful match\n" );
        for ( size_t i = 0; i < matchCount; i++ )
        {
            if ( pmatch[i].rm_so >= 0 )
            {
                fprintf( stdout, "match %zu (start: %3lu; end: %3lu): %*.*s\n", i,
                    (unsigned long) pmatch[i].rm_so,
                    (unsigned long) pmatch[i].rm_eo,
                    (int) ( pmatch[i].rm_eo - pmatch[i].rm_so ),
                    (int) ( pmatch[i].rm_eo - pmatch[i].rm_so ),
                    text + pmatch[i].rm_so );
            }
        }
    }
    return 0;
}

这是输出:

Number of subexpressions: 4
Sucessful match
match 0 (start:   0; end:  98): Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv
match 1 (start:  10; end:  15): apple
match 2 (start:  33; end:  39): orange
match 3 (start:  56; end:  65): bestfruit
match 4 (start:  84; end:  94): watermelon

如果要复制匹配的字符串,则需要使用strncpy并确保正确终止字符串:

char matched_string[MAX_STRING_LENGTH + 1] = {0};
...
size_t length = pmatch[1].rm_eo - pmatch[1].rm_so;
strncpy( matched_string, text + pmatch[1].rm_so, length );

 /**
  * Make sure string is 0 terminated
  */
 matched_string[ length ] = 0;

答案 5 :(得分:0)

  

有人可以告诉我如何在此字符串中逐个打印每个单词吗?

请避免使用strtok(),因为它会修改字符串并且不需要。


考虑如何解析输入字符串(好像是常量),一次解析一个子字符串。与您一样,查找"am",然后使用'/'查找2个定界符strchr()。如果找到所有内容,请更新输入字符串中的位置以再次查找,然后返回指向子字符串及其长度的指针。

const char *HL_parse(const char **v, const char *key, int delimiter, int *length) {
  *length = 0;
  char *token = strstr(*v, key);
  if (token == NULL) {
    return NULL;
  }
  char *start = strchr(token + strlen(key), delimiter);
  if (start == NULL) {
    return NULL;
  }
  start++;
  char *end = strchr(start, delimiter);
  if (end == NULL) {
    return NULL;
  }
  *v = end + 1;
  *length = (int)(end-start);
  return start;
}

使用"%.*s"打印子字符串。这样一来,最多可以打印不包含空字符的字符数组。

void HL_print_words(const char *v, const char *key, int delimiter) {
  char *sep = "";
  int length;
  const char *token;
  while ((token = HL_parse(&v, key, delimiter, &length)) != NULL) {
    printf("%s\"%.*s\"%s", sep, length, token);
    sep = " ";

  }
  printf("\n");
}

样品

int main(void) {
  HL_print_words(
      "Svnsv am /apple/ rv dbndkbrb am /orange/ rv dbundib am /bestfruit/ rv drbrnboie am /watermelon/ rv",
      "am", '/');
}

输出

"apple" "orange" "bestfruit" "watermelon"