在 Linux 进程的学习中,命令行参数 与环境变量是连接用户、应用程序与操作系统的重要桥梁。它们不仅决定了程序的启动方式,还为程序运行提供了必要的环境配置。本文将结合底层原理与实际案例,详细拆解命令行参数的传递机制、环境变量的核心特性及二者的实践应用,帮助大家彻底掌握这部分知识点。
1. 环境变量
环境变量是操作系统中用于指定运行环境的参数,具有全局属性,可被子进程继承,为程序提供无需硬编码的配置信息。
1.1 环境变量的核心概念
定义: 环境变量是键值对形式的字符串(如PATH=/usr/bin),用于指定命令搜索路径、用户主目录、终端类型等核心配置。
全局属性: 环境变量由父进程传递给子进程,修改父进程的环境变量会影响所有后续创建的子进程,但不会影响已存在的进程。
**本质:**每个程序启动时,操作系统会为其创建一张环境变量表(字符指针数组),程序通过该表获取环境配置。
单单这样说肯定比较难以理解,我们来举个例子:为什么我们在执行自己的文件时需要在前面加上路径,而执行系统命令时则不需要呢?要知道,系统命令其实也是一种文件,它们和我们自己的文件有什么不同导致了这种情况呢?

这其实就与我们的环境变量中的PATH有关了,PATH下配置了许多路径,如果你不加路径直接执行文件,那么系统就会去PATH文件下的路径去查找文件,如果找到了就会直接执行,找不到就说文件不存在。
我们可以用echo $PATH来查找它的配置:

系统中像ls等普通用户使用的命令一般放在/usr/bin目录下,我们也可以看到:


在其中我们就可以找到ls命令,而这里路径就在PATH下记录着,这正好印证了我们之前所说的。
环境变量就是像PATH这样能指定我们系统运行环境的一些参数。
以下是常见的环境变量

1.2 环境变量的操作指令
在终端中,我们可以通过以下指令直接操作环境变量:
(1)查看环境变量
**echo $NAME:**查看指定环境变量的值(NAME 为环境变量名)
echo $PATH # 查看命令搜索路径
echo $HOME # 查看用户主目录

**env:**显示所有环境变量(不含本地 Shell 变量)

**set:**显示所有环境变量和本地 Shell 变量

何为本地Shell变量呢,其实就是我们在命令行中直径定义的变量,如:

这个变量i就是我们的本地Shell变量,使用set后即可查到。本地Shell变量不会被子进程继承,只有用export导出后才会成为环境变量。
**unset NAME:**删除指定环境变量
unset命令既可以清除指定环境变量,也可以清除本地Shell变量。
unset命令清除指定环境变量(如PATH)后,再重启终端又会再恢复,除非我们直接修改其配置文件(如~/.bashrc),这是为什么呢?其实系统中有一些配置文件,这些文件里就加载了对环境变量的配置,这也是我们最初的默认的环境变量配置的由来,每次重启终端环境变量就会读取这些配置文件中的配置,如PATH读取后就会恢复到删除前的状态,所以除非我们修改配置文件,不然是无法永久修改环境变量的。

(2)设置环境变量
临时设置(仅当前终端有效,关闭终端失效):
MY_ENV="hello world" # 定义本地变量,不被子进程继承
export MY_ENV="hello world" # 导出为环境变量,被子进程继承
上面的是我们新增一个环境变量的方法,我们还可以修改环境变量值,如PATH,使用图中的方法即可,最好不要直接用PATH=你的路径,不然会覆盖掉原本的PATH值(重启后会恢复):

更改PATH后,我们的文件code也可以直接运行啦,不需要再加路径了。
永久设置(需修改配置文件):
对当前用户有效: 修改~/.bashrc或~/.bash_profile
**对所有用户有效:**修改/etc/profile或/etc/environment步骤:
编辑配置文件 :vim ~/.bashrc
添加环境变量: export MY_ENV="hello world"
**生效配置:**source ~/.bashrc(无需重启终端)
想要永久生效的话,就修改我们上面所说的配置文件就可以了,可以增加新的环境变量,也可以对环境变量进行更改,如在在配置文件中的PATH后加上我们的路径,以后我们每次登录终端就都可以在这个路径下直接执行文件而不用加路径了。
1.3 环境变量的组织与访问方式
(1)环境变量表
环境变量以字符指针数组的形式存储,每个指针指向一个以\0结尾的字符串(格式为键=值),数组最后一个元素为NULL,即环境变量表,结构如下:

(2)代码中访问环境变量的 3 种方式
方式 1:通过 main 函数的 env 参数
注:这里代码参数看不懂没关系,我们会在下面命令行参数进行讲解。
cpp
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
int i = 0;
// 遍历所有环境变量
while (env[i] != NULL)
{
printf("env[%d] -> %s\n", i, env[i]);
i++;
}
return 0;
}

方式 2:通过全局变量 environ
cpp
#include <stdio.h>
int main(int argc, char* argv[])
{
extern char** environ; // 声明全局环境变量表
int i = 0;
while (environ[i] != NULL)
{
printf("%s\n", environ[i]);
i++;
}
return 0;
}
这里的environ 其实就是我们上面图中的char* env[],我们将其打印出来即可。
方式 3:通过 getenv 函数(推荐,精准获取指定变量)
getenv是系统提供的专门用于获取环境变量的函数,原型如下:
cpp
#include <stdlib.h>
char* getenv(const char* name); // name为环境变量名,返回对应值的指针
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 获取PATH环境变量
char* path = getenv("PATH");
if (path != NULL)
{
printf("PATH: %s\n", path);
}
// 获取自定义环境变量MY_ENV
char* my_env = getenv("MY_ENV");
if (my_env != NULL)
{
printf("MY_ENV: %s\n", my_env);
}
return 0;
}

1.4 环境变量的继承特性
环境变量的核心特性之一是被子进程继承,这意味着父进程的环境变量会自动传递给它创建的子进程。我们可以通过以下案例验证:
父进程代码(code3.c):
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
// 设置环境变量
setenv("MY_ENV", "hello from parent", 1); // 1表示覆盖已存在的变量
// 创建子进程
pid_t pid = fork();
if (pid == 0)
{
// 子进程:获取环境变量
char* env = getenv("MY_ENV");
printf("子进程获取的MY_ENV: %s\n", env);
}
else if (pid > 0)
{
// 父进程等待子进程结束
wait(NULL);
printf("父进程执行完毕\n");
}
return 0;
}
运行结果:

可以看到,子进程成功获取了父进程设置的环境变量。需要注意的是:
• 仅export导出的环境变量会被子继承,本地变量(未 export)不会;
• 子进程修改环境变量不会影响父进程(进程独立性)。
2. 命令行参数
我们平时运行程序时,除了输入可执行文件名称,还会添加诸如-l、--help之类的参数,这些就是命令行参数。它们本质上是程序启动时从命令行接收的输入数据,让程序可以根据不同参数执行不同逻辑。
2.1 main 函数的隐藏参数
很多人误以为main函数是无参的,但实际上它有三个核心参数,专门用于接收命令行参数和环境变量,其完整原型如下:
cpp
int main(int argc, char *argv[], char *env[]);
• argc(argument count): 命令行有效参数的个数,至少为 1(默认包含可执行文件自身路径)。
• argv(argument vector): 字符串指针数组,每个元素指向一个命令行参数,最后一个元素为NULL(作为结束标志)。
• env(environment vector): 环境变量指针数组,每个元素指向一个环境变量字符串,其实就是我们上面说的环境变量表。
argc就是我们的命令行参数个数,如我们要运行code,就会输入./code -a 等参数,它默认第一个为./code,之后的是我们的额外指令,此时argc为2。
argv其实就类似于我们的环境变量表,它就是一个**命令行参数表,**假如我们输入
./code -a -b -c,那么命令行参数表大致如下:

2.2 命令行参数的传递机制
通过一个简单案例可以直观理解参数传递过程:
cpp
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("有效参数个数:%d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("argv[%d] -> %s\n", i, argv[i]);
}
return 0;
}
运行结果:

可以看到:
argv[0]固定指向可执行文件的路径(这里是./code4);
后续元素按输入顺序依次存储命令行参数;
参数之间以空格分隔,若参数包含空格需用引号包裹(如./code4 "hello world")。
2.3 命令行参数的实际用途
命令行参数是程序的 "开关",常见用途包括:
功能选择: 如ls -l(长格式显示)、ls -a(显示隐藏文件);
配置指定: 如gcc -o output test.c(指定输出文件名);
**模式切换:**如程序的调试模式./app --debug与生产模式./app --release。
2.4 命令行参数与环境变量的关联
核心关联:程序启动时的参数传递流程
当我们在终端执行./test -a -b时,背后的流程的是:
终端(父进程)将命令行参数和自身的环境变量打包;
通过exec系统调用启动test程序(子进程);
操作系统为test创建进程控制块(PCB)、虚拟地址空间;
将命令行参数存入argv数组,环境变量存入env数组,传递给main函数;
main函数接收参数并执行相应逻辑。
结语
好好学习,天天向上!有任何问题请指正,谢谢观看!