Extracting numbers from the string using regex

Viewed 303

I am trying to extract the number 4 and 3 from the string /ab/cd__my__sep__4__some__sep__3. I am trying with regex but not sure how would I do this. I wrote the following code, but it just prints out __my__sep__4__some__sep__3

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

int main() {
    char* s = "/ab/cd__my__sep__4__some__sep__3";
    regex_t regex;
    int reti = regcomp(&regex,"__my__sep__([0-9]+)",REG_EXTENDED);
    if(reti!=0) {
        exit(-1);
    }else {
        regmatch_t match[2];
        reti = regexec(&regex, s, 2, match, 0);
        if(reti == 0) {
            char *v = &s[match[1].rm_so];
            ssize_t fl;
            sscanf(v, "%zu", &fl);
            printf("%s",v);
        }else {
            printf("else");
        }
    }
}

How could I extract the numbers 4 and 3 ?

3 Answers

match[0] refers to the part of the text matched by the entire pattern. match[1] is the match corresponding to the first capture (parenthesized subpattern).

Note that &s[match[1].rm_so] gives you a pointer to the start of the capture, but if you print the string at that point, you will get the part of the string starting at the beginning of the capture. In this case, that doesn't really matter. Since you're using sscanf to extract the integer value of the captured text, the fact that the substring isn't terminated immediately doesn't matter; it's not going to be followed by a digit, and sscanf will stop at the first non-digit.

But in the general case, it's possible that it will not be so easy to identify the end of the matched capture, and you can use one of these techniques:

If you want to print the capture, you can use a computed string width format: (See Note 1.)

printf("%.*s\n", match[1].rm_eo - match[1].rm_so, &s[match[1].rm_so]);

If you have strndup, you can easily create a dynamically-allocated copy of the capture: (See Note 2.)

char* capture = strndup(&s[match[1].rm_so], match[1].rm_eo - match[1].rm_so);

As a quick-and-dirty hack, it is also possible to just insert a NUL terminator (assuming that the searched string is not immutable, which means that it cannot be a string literal). You'll probably want to save the old value of the following character so that you can restore the string to it's original state:

char* capture = &s[match[1].rm_so];
char* rest = &s[match[1].rm_eo];
char saved_char = *rest;
*rest = 0;
/* capture now points to a NUL-terminated string. */
/* ... */
/* restore s */
*rest = saved_char;

None of the above is really necessary in the context of the original question, since the sscanf as written will work perfectly if you change the start of the string to scan from match[0] to match[1].

Notes:

  1. In the general case, you should test to make sure that a capture was actually found before trying to use its offset. The rm_so member will be -1 if the capture was not found during the regex search That doesn't necessarily mean that the search failed, because the capture could be part of an alternative not used in the match.

  2. Don't forget to free the copy when you no longer need it. If you don't have strndup, it's pretty easy to implement. But watch out for the corner cases.

Since you are using sscanf(), there is no need to use a regex. You can parse the two numbers from your string using sscanf() alone using the format string: "%*[^0-9]%d%*[^0-9]%d" where "%*[^0-9]" uses the assignment suppression '*' to read and discard all non-digit characters and then uses "%d" to extract the integer value. The full format-string just repeats those two patterns twice.

A short example using your input could be:

#include <stdio.h>

int main (void) {

    char *s = "/ab/cd__my__sep__4__some__sep__3";
    int a, b;

    if (sscanf (s, "%*[^0-9]%d%*[^0-9]%d", &a, &b) == 2)
        printf ("a: %d\nb: %d\n", a, b);
    else {
        fputs ("error: parse of integers failed.\n", stderr);
        return 1;
    }
}

Example Use/Output

$ ./bin/parse2ints
a: 4
b: 3

If you find yourself attempting to parse something that sscanf() cannot handle, then a regex is appropriate. Here, sscanf() is more than capable of handling your needs alone.

Create a regex format that only holds [0-9]. Then create a separate boolean function checking whether a character belongs or not to your regex. Then apply the function to your string. If true, add the character to the string you want to output

Related