Sscanf: как анализировать

Я пытаюсь разобрать эту строку:

+CGDCONT: 0, «IP», «бесплатно», «10.158.88.34»

Жирным шрифтом выделены предметы, которые меня интересуют. Моя первая попытка заключалась в использовании этого шаблона:

"+CGDCONT: %d,\"IP\",\"%*s\",\"%d.%d.%d.%d\""

с его помощью анализируется только 1 переменная.

Затем я попробовал:

"+CGDCONT: %d,\"IP\",\"free\",\"%d.%d.%d.%d\"  // the string "free" is hard coded

Это работает, но «бесплатно» не всегда может быть «бесплатно», поэтому мне нужен способ игнорировать это.

Затем я попробовал:

"+CGDCONT: %d,\"IP\",\"%s\",\"%d.%d.%d.%d\"  // the string "free" is no more optionnal

с его помощью анализируются 2 переменные. Второй (%s) анализируется как

free","10.158.88.34"\8  // \8 is ascii char 8.

Как мне поступить?

*scanf() — неподходящий инструмент для этой работы.

Dúthomhas 15.05.2024 19:14

Формат %s читает строки, разделенные пробелами. Поскольку места нет, он прочитает все.

Some programmer dude 15.05.2024 19:19

В качестве обходного пути, если вы действительно хотите использовать sscanf, возможно, используйте \"%*[^\"]\". Это будет продолжаться до конца ", но не включая его.

Some programmer dude 15.05.2024 19:20

Если вы можете рассчитывать на то, что ввод будет достаточно регулярным, и/или если вы достаточно осторожны при написании правильного формата, проверке ошибок и проверке результатов, то вы, вероятно, можете заставить scanf поработать для этого. Но это не совсем подходит. Вместо этого вам следует использовать регулярное выражение. Или, в зависимости от контекста, написать парсер мини-языка.

John Bollinger 15.05.2024 19:27

Всегда ли входная строка начинается с "+CGDCONT: "? Всегда ли элемент "10.158.88.34" в вашем примере будет стоять сразу после третьей запятой?

Andreas Wenzel 15.05.2024 19:31

Если бы "free" когда-либо стал, скажем, "free, but not cheap", я сомневаюсь, что вы смогли бы составить описатель формата scanf(), который бы работал...

Fe2O3 15.05.2024 23:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
99
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как уже упоминалось в комментарии кого-то другого, функция sscanf не очень подходит для этой задачи.

При условии, что

  • входная строка всегда начинается с "+CGDCONT: " и
  • IP-адрес всегда указывается сразу после третьей запятой, и после него больше ничего не идет,

тогда ты мог бы

  • извлеките значение «CGDCONT», проанализировав входную строку и пройдя мимо "+CGDCONT: ", а затем извлеките все до следующей запятой и
  • извлеките IP-адрес, найдя третью запятую и извлекая все, что будет потом.

Вот пример:

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

// This function will extract the value after "+CGDCONT: " up to
// the next comma. On success, it will return true, otherwise it
// will return false.
bool extract_CGDCONT_value( const char *input, char *buffer, size_t buffer_size )
{
    static const char prefix[] = "+CGDCONT: ";
    static const size_t prefix_length = sizeof prefix - 1;
    const char *p;
    size_t num_bytes_to_copy;

    // return false if input input string does not start
    // with "+CGDCONT: "
    if ( strncmp( input, prefix, prefix_length ) != 0 )
        return false;

    // make "input" point immediately after "+CGDCONT: "
    input += prefix_length;

    // find next comma, and return false if not found
    p = strchr( input, ',' );
    if ( p == NULL )
        return false;

    // calculate number of bytes to copy
    num_bytes_to_copy = p - input;

    // return false if output buffer is not large enough to store
    // the result
    if ( num_bytes_to_copy >= buffer_size )
        return false;

    // copy the result to the buffer provided by the caller
    memcpy( buffer, input, num_bytes_to_copy );

    // write terminating null character to buffer
    buffer[num_bytes_to_copy] = '\0';

    // everything went ok, so return true
    return true;
}

// This function will extract everything after the third comma.
// On success, it will return true, otherwise false.
bool extract_IP_address( const char *input, char *buffer, size_t buffer_size )
{
    size_t num_bytes_to_copy;

    // attempt to find the third comma
    for ( int i = 0; i < 3; i++ )
    {
        // attempt to find the next comma, and return false if
        // it does not exist
        input = strchr( input, ',' );
        if ( input == NULL )
            return false;

        // make "input" point one character past the comma
        input++;
    }

    // calculate number of bytes to copy
    num_bytes_to_copy = strlen( input );

    // return false if output buffer is not large enough to store
    // the result
    if ( num_bytes_to_copy >= buffer_size )
        return false;

    // copy the result to the buffer provided by the caller
    strcpy( buffer, input );

    // everything went ok, so return true
    return true;
}

int main( void )
{
    const char input[] = "+CGDCONT: 0,\"IP\",\"free\",\"10.158.88.34\"";

    char CGDCONT_value[200];
    char IP_address[200];

    if ( extract_CGDCONT_value( input, CGDCONT_value, sizeof CGDCONT_value ) )
    {
        printf( "Extracted CGDCONT value: %s\n", CGDCONT_value );
    }
    else
    {
        printf( "Failed to extract CGDCONT value.\n" );
    }

    if ( extract_IP_address( input, IP_address, sizeof IP_address ) )
    {
        printf( "Extracted IP address: %s\n", IP_address );
    }
    else
    {
        printf( "Failed to extract IP address.\n" );
    }
}

В этой программе жестко закодированы входные данные вопроса, и для этих входных данных она выдает следующий результат:

Extracted CGDCONT value: 0
Extracted IP address: "10.158.88.34"

@Fe2O3: Функция strrchr должна сначала пройти от начала до конца строки, прежде чем она сможет начать поиск последней запятой, тогда как если вы ищете третью запятую с начала, вам придется пройти меньший участок строки. С другой стороны, strchr должен сравнивать каждый символ с двумя значениями при перемещении по строке, тогда как strrchr должен сравнивать каждый символ только с одним значением, поэтому strrchr, вероятно, движется с большей скоростью. Кроме того, strrchr нужно будет вызывать только один раз, а не три, поэтому накладные расходы на функцию будут меньшей проблемой.

Andreas Wenzel 15.05.2024 23:46

@Fe2O3: Моя функция extract_IP_address выполняет одну задачу — проверяет наличие трех запятых. В противном случае функция возвращает false. Если бы я использовал strrchr вместо strchr, я бы не смог выполнить эту проверку ввода. Поэтому я думаю, что предпочитаю использовать strchr. Однако вы правы в том, что использование strrchr упростило бы код, если бы проверка ввода не требовалась.

Andreas Wenzel 16.05.2024 01:04

@Fe2O3: Да, моя проверка ввода неполная. Однако, поскольку ОП не предоставил много информации о формате, я не хотел делать слишком много предположений о формате. Если мы предположим, что ввод OP имеет формат CSV и возможно, что поле содержит запятую, то мне придется добавить проверку для каждой запятой, независимо от того, заключена она в кавычки или нет.

Andreas Wenzel 16.05.2024 11:18

CSV? Вероятно, есть 1 или 67 примеров вопросов и ответов по этому поводу... Ура! :-)

Fe2O3 16.05.2024 11:26
Ответ принят как подходящий

Если вам нужен, по сути, однострочник, вы можете сделать это следующим образом:

#include <stdio.h>

int main(void) {
    char inp[] = "+CGDCONT: 0,\"IP\",\"free\",\"10.158.88.34\"";
    int val = -1;
    int arr[4] = { -1, -1, -1, -1 };
    printf("Processing %s\n", inp);

    if (sscanf(inp, " +CGDCONT: %d ,\"IP\", %*[^,], \" %d.%d.%d.%d\"",
            &val, &arr[0], &arr[1], &arr[2], &arr[3]) != 5)
        return 1; // handle error

    printf("val = %d, arr[] = %d %d %d %d\n", val, arr[0], arr[1], arr[2], arr[3]);
}

Первая подстрока сопоставляется перед выделенным жирным шрифтом 0 или другим int, который обрабатывается (ведущий пробел предназначен для фильтрации предыдущих символов новой строки и т. д.). Следующая подстрока совпадает, следующая пропускается с помощью %*[]. Затем сопоставляются запятая и кавычка, за которыми следуют четыре значения int, разделенные точкой.

Выход программы:

Processing +CGDCONT: 0,"IP","free","10.158.88.34"
val = 0, arr[] = 10 158 88 34

Другие вопросы по теме