For linux if you call env you will get a sequence of bash variable like:

XDG_SESSION_DESKTOP=cinnamon
QT_QPA_PLATFORMTHEME=qt5ct
XDG_SESSION_TYPE=x11

What if you want to parse such files in C? I written a kv_parse_value() which might help you!

It has zero dependencies, doesn't require malloc and also can handle whitespace and quoted strings.

Typically application is linux software, embedded systems, simple configuration store... etc...

If this helps you, please do let me know!

Also it's now in github at https://github.com/mofosyne/kv_parse_value.c


In addition, I noticed that for very simple INI files this will be able to parse it as well!

You could potentially also parse some linux program output which uses a similar form... but for those kind of unstructured output it would tend to have whitespace and use : instead of =.

I have intentionally written this in a way that you can easily modify and strip it down, in case you are trying to run this in an embedded or restricted context.

Other Style Supported

Spaces and ':'

XDG_SESSION_DESKTOP   : cinnamon
QT_QPA_PLATFORMTHEME  : qt5ct
XDG_SESSION_TYPE      : x11

Quotes and Double Quotes

XDG_SESSION_DESKTOP="cinnamon"
QT_QPA_PLATFORMTHEME="qt5ct"
XDG_SESSION_TYPE="x11"

Key Features:

  • ASCII Support Only: The function only processes standard ASCII characters. Non-ASCII input may produce undefined behavior.
  • Malloc-Free: No dynamic memory allocation is used. The caller must provide a fixed-size buffer.
  • String-Only Values: Extracted values are stored as null-terminated C strings.
  • Whitespace Handling (KV_PARSE_WHITESPACE_SKIP):
    • Ignores leading and trailing spaces/tabs around keys and values.
  • Quoted String Support (KV_PARSE_QUOTED_STRINGS):
    • Allows values to be enclosed in single (') or double (") quotes.
    • Handles escaped quotes within quoted values ("example\"text").
  • Multi-Character Delimiter Support:
    • Accepts both = and : as key-value separators (e.g., "key=value" or "key: value").
  • Line-Based Parsing:
    • The function skips to the next line if the key does not match.
  • Safe String Copy:
    • Ensures the extracted value does not exceed value_max - 1 characters.

Source

/* Simple ANSI C KV Parser
 * 2025 Brian Khuu (This Code Is Released To The Public Domain)
 * To test this function run `cat kvparser.h | tcc -DKV_PARSER_TEST -run -` */
#define KV_PARSE_WHITESPACE_SKIP
#define KV_PARSE_QUOTED_STRINGS

/**
 * @brief Parses a key-value pair from a given string.
 *
 * 2025 Brian Khuu https://briankhuu.com/ (This function is dedicated to Public Domain)
 * https://briankhuu.com/blog/2025/01/30/simple-key-value-text-parser-in-c/
 *
 * This function extracts the value associated with a specified key in a formatted key-value string.
 *
 * ## Supported Features:
 * - **Whitespace Skipping** (`KV_PARSE_WHITESPACE_SKIP`): Ignores spaces and tabs around keys and values.
 * - **Quoted String Support** (`KV_PARSE_QUOTED_STRINGS`): Handles values enclosed in single (`'`) or double (`"`) quotes.
 * - **Key Delimiters**: Supports both `=` and `:` as key-value separators.
 *
 * @param str Input string containing multiple key-value pairs (e.g., "key=value") separated by newline.
 * @param key The key to search for in the input string.
 * @param value Output buffer to store the extracted value.
 * @param value_max Maximum length of the output buffer (`value`), including the null terminator.
 * @return The length of the extracted value (excluding the null terminator) on success, or `0` if the key is not found.
 *
 * @note If `KV_PARSE_WHITESPACE_SKIP` is defined, leading and trailing whitespace is ignored before returning the value.
 * @note If `KV_PARSE_QUOTED_STRINGS` is defined, values can be enclosed in single (`'`) or double (`"`) quotes.
 *
 * @example Usage Example:
 * @code
 * char buffer[50];
 * unsigned int len = kv_parse_value("username=admin\npassword=1234", "username", buffer, sizeof(buffer));
 * if (len > 0) {
 *     printf("Username: %s\n", buffer);
 * } else {
 *     printf("Key not found.\n");
 * }
 * @endcode
 */
unsigned int kv_parse_value(const char *str, const char *key, char *value, unsigned int value_max)
{
    for (; *str != '\0'; str++)
    {
#ifdef KV_PARSE_WHITESPACE_SKIP
        while (*str == ' ' || *str == '\t')
        {
            str++;
        }
#endif

        /* Check For Key */
        for (int i = 0; *str != '\0' && key[i] != '\0'; i++, str++)
        {
            /* Key Mismatched. Skip Line */
            if (*str != key[i])
            {
                goto skip_line;
            }
        }

#ifdef KV_PARSE_WHITESPACE_SKIP
        while (*str == ' ' || *str == '\t')
        {
            str++;
        }
#endif

        /* Check For Key Value Delimiter */
        if (*str != '=' && *str != ':')
        {
            goto skip_line;
        }
        str++;

#ifdef KV_PARSE_WHITESPACE_SKIP
        while (*str == ' ' || *str == '\t')
        {
            str++;
        }
#endif

        /* Copy Value To Buffer */
#ifdef KV_PARSE_QUOTED_STRINGS
        char quote = '\0';
#endif
        for (int i = 0; i < (value_max - 1); str++)
        {
            if (*str == '\0' || *str == '\r' || *str == '\n')
            {
                /* End Of Line. Trim trailing whitespace before returning the value. */
                value[i] = '\0';
#ifdef KV_PARSE_WHITESPACE_SKIP
                while (i > 0 && (value[i - 1] == ' ' || value[i - 1] == '\t'))
                {
                    i--;
                    value[i] = '\0';
                }
#endif
                return i;
            }
#ifdef KV_PARSE_QUOTED_STRINGS
            else if (quote == '\0' && (*str == '\'' || *str == '"'))
            {
                /* Start Of Quoted String */
                quote = *str;
                continue;
            }
            else if (quote != '\0' && *(str - 1) != '\\' && *str == quote)
            {
                /* End Of Quoted String. Return Value */
                value[i] = '\0';
                return i;
            }
            else if (quote != '\0' && *(str - 1) == '\\' && *str == quote)
            {
                /* Escaped Quote Character In Quoted String */
                value[i - 1] = *str;
                continue;
            }
#endif

            value[i++] = *str;
        }

        /* Value too large for buffer. Don't return a value. */
        value[0] = '\0';
        return 0;

    skip_line:
        /* Search for start of next line */
        while (*str != '\n')
        {
            if (*str == '\0')
            {
                /* End of string. Key was not found. */
                return 0;
            }
            str++;
        }
    }

    /* End of string. Key was not found. */
    return 0;
}

#ifdef KV_PARSER_TEST
#include <assert.h>
#include <stdio.h>
#include <string.h>

// Test case function
void run_tests()
{
    char buffer[100] = {0};
    int buffer_count = 0;

    // **Test 1: Basic Key-Value Retrieval**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("key1=value1\nkey2=value2", "key1", buffer, sizeof(buffer));
    assert(buffer_count == 6);
    assert(strcmp(buffer, "value1") == 0);

    // **Test 2: Retrieve Last Key**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("a=b\nc=d\ne=f\ng=hello", "g", buffer, sizeof(buffer));
    assert(buffer_count == 5);
    assert(strcmp(buffer, "hello") == 0);

    // **Test 3: Key Not Found**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("a=b\nc=d", "z", buffer, sizeof(buffer));
    assert(buffer_count == 0);

    // **Test 4: Buffer Too Small**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("longkey=longvalue", "longkey", buffer, sizeof(buffer));
    assert(buffer_count == 9);
    assert(strcmp(buffer, "longvalue") == 0);

    // **Test 5: Empty Input**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("", "anykey", buffer, sizeof(buffer));
    assert(buffer_count == 0);

    // **Test 6: Input Without Key-Value Pairs**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("randomtext\nanotherline", "key", buffer, sizeof(buffer));
    assert(buffer_count == 0);

    //  **Test 7: Handling Spaces Around Key and Value**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value(" key = value \n next = test ", "key", buffer, sizeof(buffer));
#ifdef KV_PARSE_WHITESPACE_SKIP
    assert(buffer_count == 5);
    assert(strcmp(buffer, "value") == 0);
#else
    assert(buffer_count == 0);
#endif

    // **Test 8: Duplicate Keys (Return First Occurrence)**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("x=1\nx=2\nx=3", "x", buffer, sizeof(buffer));
    assert(buffer_count == 1);
    assert(strcmp(buffer, "1") == 0);

    // **Test 9: Newline Variations (Windows vs. Unix)**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("a=one\r\nb=two", "b", buffer, sizeof(buffer));
    assert(buffer_count == 3);
    assert(strcmp(buffer, "two") == 0);

    // **Test 10: Key With Special Characters**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("user-name=admin\nuser@domain.com=me", "user-name", buffer, sizeof(buffer));
    assert(buffer_count == 5);
    assert(strcmp(buffer, "admin") == 0);

    // **Test 11: Value Containing '='**
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("path=/home/user=data", "path", buffer, sizeof(buffer));
    assert(buffer_count == 15);
    assert(strcmp(buffer, "/home/user=data") == 0);

    // **Test 12: Quoted String **
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("path=\"/home/user=data\"", "path", buffer, sizeof(buffer));
#ifdef KV_PARSE_QUOTED_STRINGS
    assert(buffer_count == 15);
    assert(strcmp(buffer, "/home/user=data") == 0);
#else
    assert(buffer_count == 17);
    assert(strcmp(buffer, "\"/home/user=data\"") == 0);
#endif

    // **Test 13: Uncapped Quoted String **
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("path=\"/home/user=data", "path", buffer, sizeof(buffer));
#ifdef KV_PARSE_QUOTED_STRINGS
    assert(buffer_count == 15);
    assert(strcmp(buffer, "/home/user=data") == 0);
#else
    assert(buffer_count == 16);
    assert(strcmp(buffer, "\"/home/user=data") == 0);
#endif

    // **Test 14: Quoted String With Escaped Quote **
    memset(buffer, 0, sizeof(buffer));
    buffer_count = kv_parse_value("path=\"/home/\\\"user=data\"", "path", buffer, sizeof(buffer));
#ifdef KV_PARSE_QUOTED_STRINGS
    assert(buffer_count == 16);
    assert(strcmp(buffer, "/home/\"user=data") == 0);
#else
    assert(buffer_count == 19);
    assert(strcmp(buffer, "\"/home/\\\"user=data\"") == 0);
#endif

    printf("kv_parse_value() passed successfully!\n");
}

// Run tests in main()
int main()
{
    run_tests();
    return 0;
}
#endif

When you run the above source in tcc via cat kvparser.h | tcc -DKV_PARSER_TEST -run -

kv_parse_value() passed successfully!