【问题标题】:Parse string into array based on spaces or "double quotes strings"根据空格或“双引号字符串”将字符串解析为数组
【发布时间】:2012-03-11 22:50:05
【问题描述】:

我试图将用户输入字符串解析为一个名为 char *entire_line[100]; 的数组;其中每个单词都放在数组的不同索引处,但如果字符串的一部分用引号封装,则应将其放在单个索引中。 所以如果我有

char buffer[1024]={0,};
fgets(buffer, 1024, stdin);

示例输入:“word filename.txt”这是一个字符串,应该在输出数组中占据一个索引”;

tokenizer=strtok(buffer," ");//break up by spaces
        do{
            if(strchr(tokenizer,'"')){//check is a word starts with a "
            is_string=YES;
            entire_line[i]=tokenizer;// if so, put that word into current index
            tokenizer=strtok(NULL,"\""); //should get rest of string until end "
            strcat(entire_line[i],tokenizer); //append the two together, ill take care of the missing space once i figure out this issue

              }  
        entire_line[i]=tokenizer;
        i++;
        }while((tokenizer=strtok(NULL," \n"))!=NULL);

这显然是行不通的,只有当双引号封装的字符串位于输入字符串的末尾时才会关闭 但我可以 输入:单词“这是用户输入的文本” filename.txt 一直试图解决这个问题,总是卡在某个地方。 谢谢

【问题讨论】:

  • 您需要考虑的另一件事是数据可能需要包含双引号字符,因此您可能必须对该字符进行某种转义。当然,在基本的带引号的字符串工作正常之前,可以推迟该支持,但请记住这一点......

标签: c parsing split strtok


【解决方案1】:

我前段时间写了一个qtok 函数,它从字符串中读取引用的单词。它不是一个状态机,它不会让你成为一个数组,但将生成的令牌放入一个数组中是微不足道的。它还处理转义的引号以及尾随和前导空格:

#include <stdio.h>
#include <ctype.h>
#include <assert.h>

// Strips backslashes from quotes
char *unescapeToken(char *token)
{
    char *in = token;
    char *out = token;

    while (*in)
    {
        assert(in >= out);

        if ((in[0] == '\\') && (in[1] == '"'))
        {
            *out = in[1];
            out++;
            in += 2;
        }
        else
        {
            *out = *in;
            out++;
            in++; 
        }
    }
    *out = 0;
    return token;
}

// Returns the end of the token, without chaning it.
char *qtok(char *str, char **next)
{
    char *current = str;
    char *start = str;
    int isQuoted = 0;

    // Eat beginning whitespace.
    while (*current && isspace(*current)) current++;
    start = current;

    if (*current == '"')
    {
        isQuoted = 1;
        // Quoted token
        current++; // Skip the beginning quote.
        start = current;
        for (;;)
        {
            // Go till we find a quote or the end of string.
            while (*current && (*current != '"')) current++;
            if (!*current) 
            {
                // Reached the end of the string.
                goto finalize;
            }
            if (*(current - 1) == '\\')
            {
                // Escaped quote keep going.
                current++;
                continue;
            }
            // Reached the ending quote.
            goto finalize; 
        }
    }
    // Not quoted so run till we see a space.
    while (*current && !isspace(*current)) current++;
finalize:
    if (*current)
    {
        // Close token if not closed already.
        *current = 0;
        current++;
        // Eat trailing whitespace.
        while (*current && isspace(*current)) current++;
    }
    *next = current;

    return isQuoted ? unescapeToken(start) : start;
}

int main()
{
    char text[] = "   \"some text in quotes\"    plus   four simple words p\"lus something strange\" \"Then some quoted \\\"words\\\", and backslashes: \\ \\ \"  Escapes only work insi\\\"de q\\\"uoted strings\\\"   ";

    char *pText = text;

    printf("Original: '%s'\n", text);
    while (*pText)
    {
        printf("'%s'\n", qtok(pText, &pText));
    }

}

输出:

Original: '   "some text in quotes"    plus   four simple words p"lus something strange" "Then some quoted \"words\", and backslashes: \ \ "  Escapes only work insi\"de q\"uoted strings\"   '
'some text in quotes'
'plus'
'four'
'simple'
'words'
'p"lus'
'something'
'strange"'
'Then some quoted "words", and backslashes: \ \ '
'Escapes'
'only'
'work'
'insi\"de'
'q\"uoted'
'strings\"'

【讨论】:

    【解决方案2】:

    Torek 解析代码的部分非常出色,但使用起来几乎不需要更多工作。

    为了我自己的目的,我完成了c函数。
    在这里分享我基于Torek's code的工作。

    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    size_t split(char *buffer, char *argv[], size_t argv_size)
    {
        char *p, *start_of_word;
        int c;
        enum states { DULL, IN_WORD, IN_STRING } state = DULL;
        size_t argc = 0;
    
        for (p = buffer; argc < argv_size && *p != '\0'; p++) {
            c = (unsigned char) *p;
            switch (state) {
            case DULL:
                if (isspace(c)) {
                    continue;
                }
    
                if (c == '"') {
                    state = IN_STRING;
                    start_of_word = p + 1; 
                    continue;
                }
                state = IN_WORD;
                start_of_word = p;
                continue;
    
            case IN_STRING:
                if (c == '"') {
                    *p = 0;
                    argv[argc++] = start_of_word;
                    state = DULL;
                }
                continue;
    
            case IN_WORD:
                if (isspace(c)) {
                    *p = 0;
                    argv[argc++] = start_of_word;
                    state = DULL;
                }
                continue;
            }
        }
    
        if (state != DULL && argc < argv_size)
            argv[argc++] = start_of_word;
    
        return argc;
    }
    void test_split(const char *s)
    {
        char buf[1024];
        size_t i, argc;
        char *argv[20];
    
        strcpy(buf, s);
        argc = split(buf, argv, 20);
        printf("input: '%s'\n", s);
        for (i = 0; i < argc; i++)
            printf("[%u] '%s'\n", i, argv[i]);
    }
    int main(int ac, char *av[])
    {
        test_split("\"some text in quotes\" plus four simple words p\"lus something strange\"");
        return 0;
    }
    

    查看程序输出:

    输入:'“引号中的一些文字”加上四个简单的单词“加上一些奇怪的东西”'
    [0] '引号中的一些文字'
    [1] '加号'
    [2] '四'
    [3] '简单'
    [4] '话'
    [5] 'p'lus'
    [6] '某事'
    [7] '奇怪的''

    【讨论】:

    • 请问,你为什么标记*p = 0 ?它改变了原始缓冲区
    • 从已解析的令牌中生成以空字符结尾的字符串
    【解决方案3】:

    strtok 函数在 C 中是一种糟糕的标记方式,除了一个(公认的常见)情况:简单的空格分隔的单词。 (即便如此,由于缺乏重入和递归能力,它仍然不是很好,这就是为什么我们在早前就为 BSD 发明了strsep。)

    在这种情况下,最好的办法是构建自己的简单状态机:

    char *p;
    int c;
    enum states { DULL, IN_WORD, IN_STRING } state = DULL;
    
    for (p = buffer; *p != '\0'; p++) {
        c = (unsigned char) *p; /* convert to unsigned char for is* functions */
        switch (state) {
        case DULL: /* not in a word, not in a double quoted string */
            if (isspace(c)) {
                /* still not in a word, so ignore this char */
                continue;
            }
            /* not a space -- if it's a double quote we go to IN_STRING, else to IN_WORD */
            if (c == '"') {
                state = IN_STRING;
                start_of_word = p + 1; /* word starts at *next* char, not this one */
                continue;
            }
            state = IN_WORD;
            start_of_word = p; /* word starts here */
            continue;
    
        case IN_STRING:
            /* we're in a double quoted string, so keep going until we hit a close " */
            if (c == '"') {
                /* word goes from start_of_word to p-1 */
                ... do something with the word ...
                state = DULL; /* back to "not in word, not in string" state */
            }
            continue; /* either still IN_STRING or we handled the end above */
    
        case IN_WORD:
            /* we're in a word, so keep going until we get to a space */
            if (isspace(c)) {
                /* word goes from start_of_word to p-1 */
                ... do something with the word ...
                state = DULL; /* back to "not in word, not in string" state */
            }
            continue; /* either still IN_WORD or we handled the end above */
        }
    }
    

    请注意,这并没有考虑到单词中出现双引号的可能性,例如:

    "some text in quotes" plus four simple words p"lus something strange"
    

    通过上面的状态机,您会看到"some text in quotes" 变成了单个标记(忽略双引号),但p"lus 也是单个标记(包括引号),something 是单个令牌,strange" 是一个令牌。无论你想要这个,或者你想如何处理它,都取决于你。对于更复杂但更彻底的词法标记,您可能需要使用 flex 之类的代码构建工具。

    另外,当for 循环退出时,如果state 不是DULL,您需要处理最后一个字(我在上面的代码中省略了这个)并决定如果state 是怎么办IN_STRING(表示没有双引号)。

    【讨论】:

    • 出于好奇,为什么打电话给continue 而不是break?如果开发人员要在 switch 之后添加代码,他们可能会对结果感到困惑,因为它被 continue 调用短路了。
    • @ktbiz:只是一种风格偏好。在实际代码中,无论状态如何,我都可能会break,而当需要移动到下一个输入字符时,我可能会continue,尽管细节总是会有所不同。
    【解决方案4】:

    我认为您的问题的答案实际上相当简单,但我假设其他回答似乎采取了不同的回答。我假设您希望将任何带引号的文本块单独分开,而不管间距如何,其余文本由空格分隔。

    所以举个例子:

    “引号中的一些文字”加上四个简单的单词“加上一些奇怪的东西”

    输出将是:

    [0] 引号中的一些文本

    [1] 加

    [2] 四个

    [3] 简单

    [4] 个字

    [5]p

    [6] 有一些奇怪的东西

    鉴于这种情况,只需要一点简单的代码,不需要复杂的机器。您将首先检查第一个字符是否有前导引号,如果有,请勾选标志并删除该字符。以及删除字符串末尾的任何引号。然后根据引号对字符串进行标记。然后用空格标记先前获得的所有其他字符串。如果没有前导引号,则从获得的第一个字符串开始标记化,如果有前导引号,则从获得的第二个字符串开始。然后,第一部分中剩余的每个字符串都将添加到字符串数组中,其中散布着第二部分中的字符串,以代替它们被标记的字符串。这样就可以得到上面列出的结果。在代码中,这看起来像:

    #include<string.h>
    #include<stdlib.h>
    
    char ** parser(char * input, char delim, char delim2){
        char ** output;
        char ** quotes;
        char * line = input;
        int flag = 0;
        if(strlen(input) > 0 && input[0] == delim){
            flag = 1;
            line = input + 1;
        }
        int i = 0;
        char * pch = strchr(line, delim);
        while(pch != NULL){
            i++;
            pch = strchr(pch+1, delim);
        }
        quotes = (char **) malloc(sizeof(char *)*i+1);
        char * token = strtok(input, delim);
        int n = 0;
        while(token != NULL){
            quotes[n] = strdup(token);
            token = strtok(NULL, delim);
            n++;
        }
        if(delim2 != NULL){
            int j = 0, k = 0, l = 0;
            for(n = 0; n < i+1; n++){
                if(flag & n % 2 == 1 || !flag & n % 2 == 0){
                    char ** new = parser(delim2, NULL);
                    l = sizeof(new)/sizeof(char *);
                    for(k = 0; k < l; k++){
                        output[j] = new[k];
                        j++;
                    }
                    for(k = l; k > -1; k--){
                        free(new[n]);
                    }
                    free(new);
                } else {
                    output[j] = quotes[n];
                    j++;
                }
            }
            for(n = i; n > -1; n--){
                free(quotes[n]);
            }
            free(quotes);
        } else {
            return quotes;
        }
        return output;
    }
    
    int main(){
        char * input;
        char ** result = parser(input, '\"', ' ');
    
        return 0;
    }
    

    (可能不完美,我没有测试过)

    【讨论】:

      猜你喜欢
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多