Environment Variables in C
Read and modify environment variables in C! Learn getenv(), setenv(), unsetenv(), environ array, and how to configure program behavior via environment.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Read environment variables with getenv()
- ✓Modify environment with setenv()/unsetenv()
- ✓Access the environ array
- ✓Use environment for program configuration
?Why Use Environment Variables?
Environment variables let you configure programs without changing code or passing command-line arguments. They're used for paths, API keys, feature flags, and more!
Configuration
Settings without code
Secrets
API keys, passwords
System Info
PATH, HOME, USER
How Environment Variables Flow
Shell
Defines vars
OS
Passes to child
Your Program
Reads with getenv()
Every process inherits environment from its parent!
01Common Environment Variables
| Variable | Description | Example Value |
|---|---|---|
| PATH | Directories for executables | /usr/bin:/usr/local/bin |
| HOME | User's home directory | /home/user |
| USER | Current username | john |
| SHELL | Default shell | /bin/bash |
| PWD | Current directory | /home/user/project |
| LANG | Language/locale | en_US.UTF-8 |
02Reading with getenv()
getenv() returns a pointer to the value of an environment variable, or NULL if it doesn't exist.
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // Get single variable6 char* home = getenv("HOME");7 if (home != NULL) {8 printf("Home directory: %s\n", home);9 }10 11 // Get with default fallback12 char* editor = getenv("EDITOR");13 printf("Editor: %s\n", editor ? editor : "nano");14 15 // Check if variable exists16 if (getenv("DEBUG") != NULL) {17 printf("Debug mode enabled!\n");18 }19 20 // Read multiple variables21 const char* vars[] = {"USER", "SHELL", "PATH", "LANG"};22 for (int i = 0; i < 4; i++) {23 char* value = getenv(vars[i]);24 printf("%s = %s\n", vars[i], value ? value : "(not set)");25 }26 27 return 0;28}Output:
Editor: nano
USER = john
SHELL = /bin/bash
PATH = /usr/bin:/usr/local/bin
LANG = en_US.UTF-8
Don't Modify the Returned Pointer!
The pointer from getenv() points to internal storage. Don't modify it — make a copy with strdup() if you need to change it.
03Setting and Unsetting Variables
1#include <stdio.h>2#include <stdlib.h>34int main() {5 // Set a new variable6 // setenv(name, value, overwrite)7 setenv("MY_APP_DEBUG", "1", 1);8 printf("MY_APP_DEBUG = %s\n", getenv("MY_APP_DEBUG"));9 10 // Set only if not exists (overwrite = 0)11 setenv("MY_APP_DEBUG", "0", 0); // Won't change12 printf("MY_APP_DEBUG = %s (unchanged)\n", getenv("MY_APP_DEBUG"));13 14 // Overwrite existing15 setenv("MY_APP_DEBUG", "verbose", 1); // Will change16 printf("MY_APP_DEBUG = %s (changed)\n", getenv("MY_APP_DEBUG"));17 18 // Unset (remove) a variable19 unsetenv("MY_APP_DEBUG");20 char* val = getenv("MY_APP_DEBUG");21 printf("MY_APP_DEBUG = %s\n", val ? val : "(unset)");22 23 // Alternative: putenv (older, less safe)24 // putenv("NAME=value"); // Takes the string directly25 26 return 0;27}Changes Are Local
Changes made with setenv() only affect the current process and its children. They don't persist after the program ends or affect the parent shell.
04The environ Array
environ is a global array containing ALL environment variables as "NAME=value" strings.
1#include <stdio.h>23// Declared in unistd.h or declare yourself4extern char** environ;56int main() {7 printf("All environment variables:\n");8 printf("==========================\n");9 10 // Loop through all variables11 for (char** env = environ; *env != NULL; env++) {12 printf("%s\n", *env);13 }14 15 return 0;16}1718// Alternative: use main's third parameter19int main_with_envp(int argc, char* argv[], char* envp[]) {20 for (int i = 0; envp[i] != NULL; i++) {21 printf("%s\n", envp[i]);22 }23 return 0;24}04bSecurity Considerations
Environment variables are powerful but come with security risks. Treat them as untrusted input — they can be set by attackers in certain scenarios.
Don't Store Secrets in Code
Never hardcode API keys, passwords, or sensitive data in source code. Use environment variables instead — they keep secrets out of version control and allow different values in development vs production.
Validate Before Use
Always validate environment variable contents. An attacker might set PATH to include a malicious directory, or inject special characters into values passed to system commands. Sanitize paths, validate formats, and reject unexpected values.
✓ Safe Patterns
Use allowlists for expected values when possible. Convert numeric strings with proper error checking. Limit string lengths to prevent buffer issues. For file paths, use realpath() to resolve and validate they're within expected directories.
04cReal Project Patterns
Here are common patterns for using environment variables in production C projects:
Application Configuration
Store database connections, API endpoints, and feature flags in environment variables. This allows the same compiled binary to work in development, staging, and production with different configurations. Example: DB_HOST,API_URL, LOG_LEVEL.
Path Configuration
Use environment variables for file paths that differ between systems. CheckHOME or USERPROFILE for user-specific directories. Define custom variables like APP_CONFIG_DIRor APP_LOG_DIR for flexibility.
Debug Modes
Enable verbose logging or debugging features via environment variables. CheckDEBUG=1 or VERBOSE=trueto enable extra output. This avoids recompiling just to add debug statements.
04dCross-Platform Differences
| Aspect | Unix/Linux/macOS | Windows |
|---|---|---|
| Case Sensitivity | Case-sensitive (PATH ≠ path) | Case-insensitive |
| Path Separator | Colon (:) | Semicolon (;) |
| Home Directory | HOME | USERPROFILE or HOMEPATH |
| setenv() | Available | Use _putenv_s() instead |
Portable Code Tip
For portable code, use getenv() (works everywhere). For setting variables portably, consider wrapping platform-specific functions or use a cross-platform library. Always check documentation for each target platform.
05Practical Example: Configuration
1#include <stdio.h>2#include <stdlib.h>3#include <stdbool.h>4#include <string.h>56typedef struct {7 char* db_host;8 int db_port;9 char* api_key;10 bool debug;11} Config;1213Config load_config(void) {14 Config cfg;15 16 // Database host with default17 char* host = getenv("DB_HOST");18 cfg.db_host = host ? host : "localhost";19 20 // Database port - parse integer21 char* port = getenv("DB_PORT");22 cfg.db_port = port ? atoi(port) : 5432;23 24 // API key - required25 cfg.api_key = getenv("API_KEY");26 if (cfg.api_key == NULL) {27 fprintf(stderr, "Error: API_KEY not set!\n");28 exit(1);29 }30 31 // Debug mode - check if set to "1" or "true"32 char* debug = getenv("DEBUG");33 cfg.debug = debug && (strcmp(debug, "1") == 0 || 34 strcmp(debug, "true") == 0);35 36 return cfg;37}3839int main() {40 Config cfg = load_config();41 42 printf("Configuration loaded:\n");43 printf(" DB Host: %s\n", cfg.db_host);44 printf(" DB Port: %d\n", cfg.db_port);45 printf(" API Key: %s***\n", cfg.api_key[0] ? 46 (char[]){''', cfg.api_key[0], ''', '\0'} : "");47 printf(" Debug: %s\n", cfg.debug ? "enabled" : "disabled");48 49 return 0;50}Run with:
DB_HOST=db.example.com DB_PORT=3306 API_KEY=secret123 DEBUG=1 ./config_example!Code Pitfalls: Common Mistakes & What to Watch For
Copied code often uses getenv() without checking for NULL returns or validating the content, leading to crashes and security vulnerabilities.
If you search online to "read a config value from an environment variable," it might give you char *path = getenv("CONFIG_PATH"); and immediately use it. But if the variable isn't set, path is NULL, and using it causes a crash. Worse, an attacker can set malicious values.
The Trap: Online sources treat environment variables like reliable configuration, but they're actually untrusted external input. Copied code often forgets to check for NULL, provide default values, or validate that the content is safe (no path traversal, SQL injection, etc.).
The Reality: Always check getenv() returns for NULL. Provide sensible defaults. Validate the format and content of environment variables before use. Never pass them directly to system(), file operations, or string formatting without sanitization.
07Frequently Asked Questions
Q:Why does getenv() return NULL?
A: The variable isn't set in the current environment. Always check for NULL before using the returned pointer, and provide default values when appropriate.
Q:Do changes with setenv() affect the shell after my program exits?
A: No! Changes only affect the current process and its children. The parent shell's environment is unchanged. To persist changes, you'd need to modify shell config files like .bashrc.
Q:Is it safe to modify the string returned by getenv()?
A: No! The returned pointer points to internal environment storage. Modifying it causes undefined behavior. If you need to change a value, use setenv() or make a copy of the string first.
Q:How do I pass environment variables to my C program?
A: Set them before running:VAR=value ./program (Unix) orset VAR=value && program (Windows). You can also export them first or use your IDE's run configuration.
Q:Are environment variables inherited by child processes?
A: Yes! When you callfork() or system(), the child process inherits a copy of the parent's environment. Changes in the child don't affect the parent. This is how configuration propagates through process trees.
Q:What's the difference between setenv() and putenv()?
A: setenv() copies the name and value, so the original strings can be freed.putenv() uses the string directly (no copy), so you must not modify or free it. Prefer setenv() for safety.
Q:Are environment variables secure for storing secrets?
A: Environment variables are commonly used for configuration, but they're not truly secure. Other processes running as the same user can often read them. For sensitive data like passwords or API keys, consider using a secrets manager or encrypted configuration files.
Q:Can I iterate over all environment variables?
A: Yes! Use the environ global variable (declared in unistd.h or implicitly available). It's a NULL-terminated array of strings in the format "NAME=value". Loop until you hit NULL.
07Summary
Key Functions:
getenv(name)— Read variablesetenv(name,val,ow)— Set variableunsetenv(name)— Remove variable
environ— All variables array- Always check for NULL returns
- Changes only affect current process
08Configuration Best Practices
Use Environment Variables for Configuration
Store database URLs, API endpoints, and feature flags in environment variables. This follows the twelve-factor app methodology and makes your code work in different environments without changes.
Test Your Knowledge
Related Tutorials
Memory Debugging Tools
Find memory bugs with professional tools! Learn Valgrind, AddressSanitizer (ASan), and how to detect memory leaks, buffer overflows, and use-after-free errors.
Signals and Signal Handling
Handle UNIX/Linux signals in C! Learn about SIGINT, SIGTERM, SIGSEGV, signal handlers, sigaction, and writing signal-safe code.
Pointers in C
The most powerful feature in C! Pointers store memory addresses. Learn what they are, why they matter, and how to use them safely.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!