Chapter 13: Intermediate Topics: Command-Line Arguments and Environment Variables
C programs are often run in terminal or shell environments, making direct interaction with the execution context crucial. This interaction primarily happens through command-line arguments and environment variables. Understanding these mechanisms allows you to write flexible programs that can be configured at runtime and integrate seamlessly into larger system scripts or workflows.
In this chapter, we will deepen our understanding of:
- Command-line arguments (
argc,argv): How to parse and utilize inputs provided directly when executing your program. - Environment variables (
getenv,putenv,setenv): How programs can read and sometimes modify system-wide or user-specific configuration settings.
13.1 Command-Line Arguments (argc and argv)
We briefly introduced argc and argv in Chapter 12 with function pointers. Let’s explore them in more detail.
The main function serves as the entry point of a C program and can accept two parameters to receive command-line arguments:
int main(int argc, char *argv[]) {
// ...
return 0;
}
argc(Argument Count): An integer that stores the number of arguments passed to the program. This always includes the program’s name itself, soargcis always at least1.argv(Argument Vector): An array of character pointers (char *[]). Each elementargv[i]is a C-style string (achar *terminated by\0) representing one of the command-line arguments.argv[0]: Points to the name of the executable program.argv[1]: Points to the first argument provided by the user.- …
argv[argc - 1]: Points to the last argument.argv[argc]: Is guaranteed to be aNULLpointer, which can be useful for iterating.
Example: Basic Command-Line Argument Processing
#include <stdio.h>
#include <stdlib.h> // For atoi()
#include <string.h> // For strcmp()
int main(int argc, char *argv[]) {
printf("Program name: %s\n", argv[0]);
printf("Number of arguments (argc): %d\n", argc);
if (argc < 2) {
printf("Usage: %s <message> [number]\n", argv[0]);
return 1; // Indicate error
}
// Print all arguments
printf("\nAll arguments:\n");
for (int i = 0; i < argc; i++) {
printf(" Argument %d: \"%s\"\n", i, argv[i]);
}
// Accessing specific arguments
printf("\n--- Specific Argument Processing ---\n");
printf("Your message: \"%s\"\n", argv[1]);
// Optional argument processing
if (argc > 2) {
int num = atoi(argv[2]); // Convert second argument to integer
printf("The number you provided is: %d\n", num);
// Conditional logic based on arguments
if (strcmp(argv[1], "hello") == 0) {
printf("You said hello!\n");
}
} else {
printf("No optional number argument provided.\n");
}
return 0;
}
To Compile and Run:
gcc cli_args.c -o cli_args
./cli_args
./cli_args "Hello World"
./cli_args mymessage 123
./cli_args hello 456
Parsing Numerical Arguments: Arguments are always received as strings. To use them as numbers, you need to convert them:
atoi(const char *str): Converts a string to anint. Returns0on error or if the string isn’t a valid number.atof(const char *str): Converts a string to adouble.atol(const char *str): Converts a string to along int.strtol(const char *nptr, char **endptr, int base): More robust conversion forlong int. Allows error checking and specifying the number base.strtod(const char *nptr, char **endptr): More robust conversion fordouble.
Robust Argument Parsing:
For more complex command-line interfaces (e.g., with flags like -v for verbose or --help), you’d typically implement more sophisticated parsing logic or use libraries like getopt (POSIX standard) or custom solutions.
13.2 Environment Variables
Environment variables are dynamic named values that can influence the way running processes behave in a computer’s operating system. They are part of the environment in which a process runs. Common examples include PATH, HOME, USER, LANG, etc.
C programs can read environment variables using getenv() and, on some systems, modify them using putenv() or setenv().
13.2.1 getenv(): Reading Environment Variables
getenv() searches the environment list for a string that matches the provided name.
Syntax:
char *getenv(const char *name);
name: The name of the environment variable (e.g., “PATH”, “HOME”).- Returns a pointer to the value of the environment variable if found, or
NULLif not found. The returned string should not be modified, as it might point to a static buffer.
Example:
#include <stdio.h>
#include <stdlib.h> // For getenv
int main() {
char *path_env = getenv("PATH");
char *home_env = getenv("HOME");
char *user_env = getenv("USER"); // On Linux/macOS
char *username_env = getenv("USERNAME"); // On Windows
printf("--- Reading Environment Variables ---\n");
if (path_env != NULL) {
printf("PATH: %s\n", path_env);
} else {
printf("PATH environment variable not found.\n");
}
if (home_env != NULL) {
printf("HOME: %s\n", home_env);
} else {
printf("HOME environment variable not found.\n");
}
if (user_env != NULL) {
printf("USER: %s\n", user_env);
} else if (username_env != NULL) { // Try Windows equivalent
printf("USERNAME: %s\n", username_env);
} else {
printf("USER/USERNAME environment variable not found.\n");
}
char *custom_env = getenv("MY_CUSTOM_VAR");
if (custom_env != NULL) {
printf("MY_CUSTOM_VAR: %s\n", custom_env);
} else {
printf("MY_CUSTOM_VAR environment variable not found.\n");
}
return 0;
}
To Compile and Run (and set custom variable):
Linux/macOS:
gcc env_vars.c -o env_vars
./env_vars
export MY_CUSTOM_VAR="Hello From Shell"
./env_vars
unset MY_CUSTOM_VAR
Windows (Command Prompt):
cl env_vars.c
env_vars.exe
set MY_CUSTOM_VAR=Hello From Cmd
env_vars.exe
set MY_CUSTOM_VAR=
13.2.2 putenv() (POSIX.1), setenv() (POSIX.1), unsetenv() (POSIX.1): Modifying Environment Variables
These functions are part of the POSIX standard, so they might not be available on all systems (e.g., older Windows compilers might not have them by default). putenv and setenv modify the environment. unsetenv removes a variable.
int putenv(char *string);: Adds or changes an environment variable.stringmust be in the formatNAME=VALUE. The string passed toputenvshould ideally be dynamically allocated or a non-stack-allocated array, asputenvmight store a pointer to it.int setenv(const char *name, const char *value, int overwrite);: Adds or changes an environment variable. Ifoverwriteis non-zero, an existing variable is overwritten. Ifoverwriteis zero, an existing variable is not changed. More robust thanputenv.int unsetenv(const char *name);: Removes an environment variable from the environment.
Example (POSIX-compliant systems):
#include <stdio.h>
#include <stdlib.h> // For getenv, setenv, unsetenv
#include <string.h> // For strlen, strcat (for putenv)
int main() {
printf("--- Before Modification ---\n");
char *test_var = getenv("MY_TEST_VAR");
if (test_var) printf("MY_TEST_VAR: %s\n", test_var); else printf("MY_TEST_VAR not set.\n");
// Using setenv to create/overwrite
printf("\n--- Setting MY_TEST_VAR to 'InitialValue' ---\n");
if (setenv("MY_TEST_VAR", "InitialValue", 1) != 0) { // 1 to overwrite if exists
perror("setenv failed");
return 1;
}
test_var = getenv("MY_TEST_VAR");
if (test_var) printf("MY_TEST_VAR: %s\n", test_var);
// Using setenv to NOT overwrite
printf("\n--- Trying to set MY_TEST_VAR to 'NewValue' (no overwrite) ---\n");
if (setenv("MY_TEST_VAR", "NewValue", 0) != 0) { // 0 to not overwrite
perror("setenv failed");
}
test_var = getenv("MY_TEST_VAR");
if (test_var) printf("MY_TEST_VAR (should be InitialValue): %s\n", test_var);
// Using setenv to overwrite
printf("\n--- Overwriting MY_TEST_VAR to 'FinalValue' ---\n");
if (setenv("MY_TEST_VAR", "FinalValue", 1) != 0) {
perror("setenv failed");
return 1;
}
test_var = getenv("MY_TEST_VAR");
if (test_var) printf("MY_TEST_VAR: %s\n", test_var);
// Unsetting the variable
printf("\n--- Unsetting MY_TEST_VAR ---\n");
if (unsetenv("MY_TEST_VAR") != 0) {
perror("unsetenv failed");
}
test_var = getenv("MY_TEST_VAR");
if (test_var == NULL) printf("MY_TEST_VAR successfully unset.\n");
/*
// Example of putenv (less safe due to string ownership, generally prefer setenv)
printf("\n--- Using putenv (less preferred) ---\n");
char new_env_string[100];
strcpy(new_env_string, "MY_PUTENV_VAR=HelloPutenv");
if (putenv(new_env_string) != 0) { // Pass a modifiable string
perror("putenv failed");
}
char *putenv_var = getenv("MY_PUTENV_VAR");
if (putenv_var) printf("MY_PUTENV_VAR: %s\n", putenv_var);
*/
return 0;
}
Important Note on setenv and putenv:
Changes made to environment variables using setenv or putenv only affect the current process and its child processes. They do not modify the environment of the parent shell or other sibling processes. The changes are temporary for the lifetime of your program and any programs it launches.
13.2.3 The environ Pointer
On POSIX systems, there is an external global variable extern char **environ; (declared in <unistd.h> or <stdlib.h>) that points to the array of environment strings. This allows you to iterate through all environment variables.
Example:
#include <stdio.h>
#include <stdlib.h> // For environ on some systems, though <unistd.h> is more common for extern char **environ
// Declare the external environment variable array
extern char **environ;
int main() {
printf("--- All Environment Variables ---\n");
for (char **env = environ; *env != NULL; env++) {
printf("%s\n", *env);
}
return 0;
}
Exercise 13.1: Config File or Environment?
Write a C program that tries to get a “DEBUG_LEVEL” setting.
- First, attempt to read the
DEBUG_LEVELenvironment variable usinggetenv(). If found, convert it to an integer usingatoi(). - If
DEBUG_LEVELis not found in the environment, check for a command-line argument-d <level>or--debug <level>. Parse this argument. - If neither is provided, default
debug_levelto0. - Based on the final
debug_level:0: Print “No debug output.”1: Print “Basic debug output enabled.”2: Print “Detailed debug output enabled.”- Any other: Print “Invalid debug level.”
Example Execution:
# Case 1: Environment variable
export DEBUG_LEVEL=2
./config_check
# Output: Detailed debug output enabled.
unset DEBUG_LEVEL
# Case 2: Command-line argument
./config_check --debug 1
# Output: Basic debug output enabled.
# Case 3: Neither
./config_check
# Output: No debug output.
# Case 4: Invalid argument
./config_check --debug xyz
# Output: Invalid debug level.
Exercise 13.2: Custom my_echo Command (Mini-Challenge)
Implement a simplified version of the echo command (or print) that prints its command-line arguments to stdout, separated by spaces. Add a simple option to print environment variables.
Instructions:
- Your program should handle all command-line arguments and print them, separated by a single space, followed by a newline.
- If the argument
"--env"is present, instead of printing other arguments, print all environment variables (similar to theenvironexample) to the console, each on a new line. The"--env"flag should take precedence.
Example Usage:
$ ./my_echo Hello World C Programming
Hello World C Programming
$ ./my_echo --env
# (Prints all your system's environment variables)
$ ./my_echo First --env Second
# (Should only print environment variables because --env takes precedence)
You now have a solid understanding of how C programs interact with their execution environment through command-line arguments and environment variables. These are indispensable tools for creating configurable, adaptable, and system-friendly applications. In the next chapter, we’ll dive into advanced topics related to memory, specifically memory alignment and various optimization techniques.