Разобрать строку в массив на основе строк с пробелами или двойными кавычками

Я пытаюсь взять строку, вводимую пользователем, и выполнить синтаксический анализ в массиве с именем char * complete_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 Пытался понять это на время всегда где-то застреваю. Благодарность


person Airon Zagarella    schedule 11.03.2012    source источник
comment
Еще одна вещь, которую вам необходимо учитывать, - это возможность того, что данные могут потребовать включения символа двойной кавычки, поэтому вам, возможно, придется включить какое-то экранирование этого символа. Конечно, можно было бы отложить эту поддержку до тех пор, пока у вас не будет работать базовая цитируемая строка, но об этом нужно помнить ...   -  person Michael Burr    schedule 12.03.2012


Ответы (4)


Функция 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 (то есть не было закрывающих двойных кавычек ).

person torek    schedule 11.03.2012
comment
Зачем из любопытства вызывать continue вместо break? Если бы разработчик добавил код после switch, он мог бы быть сбит с толку результатами, потому что он был закорочен из-за вызовов continue. - person ktbiz; 18.12.2016
comment
@ktbiz: просто предпочтение стиля. В реальном коде я бы, вероятно, break, когда есть больше работы, независимо от состояния, и continue, когда пришло время перейти к следующему входному символу, хотя детали всегда меняются. - person torek; 18.12.2016

Части синтаксического анализа кода 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;
}

Смотрите вывод программы:

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

person Hill    schedule 13.11.2014
comment
Могу я спросить, почему вы отмечаете *p = 0? он изменяет исходный буфер - person Lê Quang Duy; 07.04.2020
comment
Сделать строку с завершающим нулем из проанализированного токена - person Hill; 07.04.2020

Некоторое время назад я написал 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\"'
person Calmarius    schedule 18.11.2015

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

Итак, учитывая пример:

«какой-то текст в кавычках» плюс четыре простых слова p «что-то странное»

Результатом будет:

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

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

person Ethan    schedule 11.11.2017