Разобрать строку в массив на основе пробелов или «строк в двойных кавычках» - PullRequest
3 голосов
/ 12 марта 2012

Я пытаюсь взять пользовательскую строку ввода и выполнить синтаксический анализ в массив с именем char * whole_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);

Это явно нене работает и подходит близко только в том случае, если инкапсулированная строка в двойных кавычках находится в конце входной строки, но я мог бы ввести: word "это текст, который будет введен пользователем" filename.txt Пытался выяснить это некоторое время, всегда где-то застрял. спасибо

Ответы [ 4 ]

9 голосов
/ 12 марта 2012

Функция strtok - ужасный способ токенизации в C, за исключением одного (общепризнанного) случая: простых слов, разделенных пробелами. (Даже тогда он все еще не очень хорош из-за отсутствия способности повторного входа и рекурсии, поэтому мы изобрели strsep для BSD еще тогда.)

Лучше всего в этом случае создать собственный простой конечный автомат:

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 (имеется в виду, что двойных кавычек не было).

5 голосов
/ 13 ноября 2014

Части кода синтаксического анализа Torek превосходны, но для их использования требуется немного больше работы.

Для своих целей я закончил функцию c.
Здесь я делюсь своей работой, основанной на коде Торека .

#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;
}

См. Вывод программы:

input: '' текст в кавычках 'плюс четыре простых слова p' lus что-то странное ''
[0] «некоторый текст в кавычках»
[1] «плюс»
[2] «четыре»
[3] «простой»
[4] «слова»
[5] 'p "lus'
[6] «что-то»
[7] 'странный' '

2 голосов
/ 18 ноября 2015

Некоторое время назад я написал функцию 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\"'
0 голосов
/ 11 ноября 2017

Я думаю, что ответ на ваш вопрос на самом деле довольно прост, но я предполагаю, что другие ответы приняли другой. Я предполагаю, что вы хотите, чтобы любой блок текста, заключенный в кавычки, выделялся сам по себе независимо от пробелов, а остальная часть текста разделялась пробелами.

Итак, приведенный пример:

"некоторый текст в кавычках" плюс четыре простых слова p "lus нечто странное"

Вывод будет:

[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;
}

(не может быть идеальным, я не проверял)

...