Skip to Content
Course content

189: Working with Environment Variables in Programs

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably noticed that hardcoding configuration—like file paths, API keys, or port numbers—directly into your source code is a recipe for disaster. Not only is it a security risk, but it means you have to recompile your entire app just to change a single timeout value. In the real world, we use environment variables for this. They let us change the behavior of a binary without touching the code.

In C, the easiest way to get these is through getenv() from <stdlib.h>. It's a simple function: you give it a string (the name of the variable), and it returns a pointer to the value. Let's build a small utility that adjusts its verbosity based on a LOG_LEVEL variable.

Pulling a value from the shell

I want my program to print detailed debug info only if LOG_LEVEL is set to "DEBUG". Otherwise, it should stay quiet. Here is how I'll start the implementation.

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

int main() {
    char *level = getenv("LOG_LEVEL");

    if (strcmp(level, "DEBUG") == 0) {
        printf("Debug mode active: Scanning system resources...\n");
    } else {
        printf("Running in standard mode.\n");
    }

    return 0;
}

On the surface, this looks fine. If I run LOG_LEVEL=DEBUG ./logger in my terminal, it works perfectly. But here is where I almost tripped up—and where you likely will too.

The dreaded NULL pointer crash

I ran the program normally, without setting the environment variable first. The result? A segmentation fault. I forgot a fundamental rule of getenv(): if the variable doesn't exist in the environment, it doesn't return an empty string; it returns NULL.

Passing NULL into strcmp() is an immediate crash because strcmp expects a valid memory address to read from. It's a classic C mistake. I need to verify the pointer exists before I even think about comparing strings.

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

int main() {
    char *level = getenv("LOG_LEVEL");

    // Fix: Always check for NULL before using the return value of getenv
    if (level == NULL) {
        printf("LOG_LEVEL not set. Defaulting to standard mode.\n");
        level = "INFO"; // Assign a default to keep the rest of the logic clean
    }

    if (strcmp(level, "DEBUG") == 0) {
        printf("Debug mode active: Scanning system resources...\n");
    } else {
        printf("Running in standard mode.\n");
    }

    return 0;
}

Handling multiple configuration keys

Usually, you aren't just checking one variable. You're checking a handful. If we add a APP_PORT variable, we have to remember that getenv() always returns a string. If you need an integer, you'll have to convert it using something like atoi().

Let's wrap this up into a slightly more robust example that handles both a string and a number.

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

int main() {
    char *level = getenv("LOG_LEVEL");
    char *port_str = getenv("APP_PORT");

    // Default values
    int port = 8080;
    int is_debug = 0;

    if (level != NULL && strcmp(level, "DEBUG") == 0) {
        is_debug = 1;
    }

    if (port_str != NULL) {
        port = atoi(port_str);
    }

    if (is_debug) {
        printf("[DEBUG] Initializing server on port %d...\n", port);
    } else {
        printf("Server started on port %d.\n", port);
    }

    return 0;
}

Now the program is resilient. It has sensible defaults, it doesn't crash when the environment is empty, and it's fully configurable from the shell. Just remember: getenv is a read-only operation. If you want to set an environment variable from within your C code to be inherited by child processes, you'll need setenv(), but that's a conversation for another time.




📋 Practical Task

Build a Database Connection String Parser

Write a program that simulates connecting to a database using environment variables. Your program must:

  • Attempt to read DB_USER, DB_PASS, and DB_HOST.
  • If any of these three variables are missing (NULL), print an error message stating exactly which variable is missing and exit the program with a non-zero status.
  • If all three are present, print a mock connection string in the format: Connecting to host [DB_HOST] as user [DB_USER]... (do NOT print the password for security reasons).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.