命令行参数与环境变量
- [一、 基本概念:它们是谁?](#一、 基本概念:它们是谁?)
-
- [1. 什么是命令行参数(Command Line Arguments)?](#1. 什么是命令行参数(Command Line Arguments)?)
- [2. 什么是环境变量(Environment Variables)?](#2. 什么是环境变量(Environment Variables)?)
- [3. 一张表看懂两者的区别](#3. 一张表看懂两者的区别)
- [二、 探秘 Linux 中最常见的环境变量](#二、 探秘 Linux 中最常见的环境变量)
-
- [1. PATH:指定命令的搜索路径(大名鼎鼎的"寻路向导")](#1. PATH:指定命令的搜索路径(大名鼎鼎的“寻路向导”))
- [2. HOME:指定用户的主工作目录(你的Linux"专属老家")](#2. HOME:指定用户的主工作目录(你的Linux“专属老家”))
- [3. SHELL:当前 Shell 的类型(你的终端"大管家")](#3. SHELL:当前 Shell 的类型(你的终端“大管家”))
- [三、 获取环境变量的方法](#三、 获取环境变量的方法)
-
- [1. 命令行操作获取](#1. 命令行操作获取)
- [2. 代码层级获取(C/C++ 核心方法)](#2. 代码层级获取(C/C++ 核心方法))
-
- [方法 1:通过系统的系统调用函数 `getenv`(最推荐、最常用)](#方法 1:通过系统的系统调用函数
getenv(最推荐、最常用)) - [方法 2:使用全局变量 extern char environ](#方法 2:使用全局变量 extern char environ)
- [方法 3:通过 `main` 函数的第三个参数 `envp` 获取](#方法 3:通过
main函数的第三个参数envp获取)
- [方法 1:通过系统的系统调用函数 `getenv`(最推荐、最常用)](#方法 1:通过系统的系统调用函数
- [四、 环境变量的组织方式](#四、 环境变量的组织方式)
-
- [1. 核心底层结构:字符指针数组(环境表)](#1. 核心底层结构:字符指针数组(环境表))
- [2. 内存布局透视:`environ` 指针的工作原理](#2. 内存布局透视:
environ指针的工作原理) - [💡 总结:两张表看懂进程的诞生](#💡 总结:两张表看懂进程的诞生)
- [五、 环境变量的全局性与进程继承机制](#五、 环境变量的全局性与进程继承机制)
-
- [1. 经典实验:验证环境变量的继承](#1. 经典实验:验证环境变量的继承)
- 2.核心原理:为什么程序能拿到这个变量?
- [💡 总结](#💡 总结)
一、 基本概念:它们是谁?
在深入代码之前,我们先用一个生动的比喻来搞懂它们:
假设你的程序是一台全自动果汁机。
- 命令行参数 就像是你在启动果汁机时,临时塞进投料口的草莓、冰块 ------它是你每次运行程序时,临时指定的、具体的处理对象。
- 环境变量 则像是果汁机所在的厨房环境配置 (比如:电压、水源开关、厨房的默认语言)------它是操作系统自带的、全局的、所有程序都能读取的配置信息。
1. 什么是命令行参数(Command Line Arguments)?
命令行参数是指在启动程序时,通过命令行(终端)直接传递给该程序的一串文本数据。
-
表现形式 :紧跟在可执行文件路径的后面,多个参数之间通常用空格分隔。
-
经典示例 :
bashgit commit -m "Fix a bug"在这个命令中,
git是可执行程序,而-m和"Fix a bug"就是传递给git的命令行参数。程序内部会接收并解析这些参数,从而知道这次是要执行"提交"操作,且提交信息为 "Fix a bug"。 -
核心特点 :临时性、针对性。它只对当前这一次程序的运行生效,下一次运行不加参数,程序就不会收到这些数据。
2. 什么是环境变量(Environment Variables)?
环境变量 是操作系统内核或 Shell 维护的、具有特定名称的键值对(Key-Value)。它们存在于操作系统的运行环境中,用来控制整个系统的行为或者为所有运行的程序提供共享配置。
- 表现形式 :由变量名和变量值组成,通常变量名全部大写。例如
PATH、USER、HOME。 - 经典示例 :
在 Linux 终端输入echo $PATH,你会看到一串由冒号分隔的路径。当你输入ls、git等命令时,系统就是去PATH变量指定的这些路径里寻找对应的可执行文件。 - 核心特点 :全局性、持久性。一旦在系统中配置好,任何在该环境下启动的程序(包括你的 Python、C++、Java 程序)都可以直接读取这些变量。
3. 一张表看懂两者的区别
为了让大家更清晰地对比,我们把它们的差异整理如下:
| 维度 | 命令行参数 (Command Line Args) | 环境变量 (Environment Variables) |
|---|---|---|
| 存在位置 | 紧跟在命令后面,作为启动数据输入 | 存在于操作系统的进程环境上下文中 |
| 生存周期 | 仅在当前单次运行期间有效(瞬时性) | 只要不删除/不关闭终端,就持续有效(持久性) |
| 可见范围 | 只有当前被启动的程序能接收到 | 在该环境下运行的所有子进程/程序都能读取 |
| 适用场景 | 传递具体的操作对象、控制开关(如:文件名、输入输出路径、是否开启调试模式 -d) |
传递全局配置、敏感凭证(如:数据库连接字符串、API 密钥、系统运行模式 NODE_ENV=production) |
二、 探秘 Linux 中最常见的环境变量
在 Linux 和 Unix 系统中,系统自带了许多默认的环境变量来维持系统的正常运转。下面我们挑选最核心、面试最常问的三个环境变量进行深度剖析。
1. PATH:指定命令的搜索路径(大名鼎鼎的"寻路向导")
-
官方定义:指定命令的搜索路径。
-
通俗解释 :
你有没有想过,为什么你在终端输入
ls、cd、mkdir时,系统就能立马执行对应的功能,而不需要你输入它们的完整路径(比如/usr/bin/ls)?这就是
PATH的功劳!PATH里面存放了一堆目录的路径。当你输入一个命令时,系统会像翻电话簿一样 ,从左到右依次去PATH列出的目录里寻找有没有同名的可执行文件。如果找了一圈都没找到,系统就会无情地抛出:command not found(未找到命令)。 -
如何查看它?
在终端中输入以下命令:
bashecho $PATH// 输出示例
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
2. HOME:指定用户的主工作目录(你的Linux"专属老家")
-
官方定义:指定用户的主工作目录(即用户登陆到 Linux 系统中时,默认进入的目录)。
-
通俗解释 :
Linux 是一个多用户操作系统。每个用户在系统里都拥有一个属于自己的"私人房间",用来存放个人的文档、代码、配置等。这个房间的绝对路径就记录在
HOME变量中。无论你当前在系统的哪个角落(比如深邃的
/var/log/nginx/目录),只要你在终端输入一个孤独的cd或者是cd ~,系统就会立刻带你**"回到老家"**,这个"家"的终点就是由HOME决定的。 -
如何查看它?
在终端中输入以下命令:
bashecho $HOME
输出示例:如果你是普通用户 moss,通常输出:/home/moss
如果你是至高无上的管理员 root,通常输出:/root
3. SHELL:当前 Shell 的类型(你的终端"大管家")
-
官方定义 :当前 Shell,它的值通常是
/bin/bash。 -
通俗解释 :
我们在 Linux 终端输入的每一行命令,并不是直接传给操作系统内核的,而是传给了一个**"翻译官"**,这个翻译官就叫 Shell。它负责把我们人类能看懂的文本命令,翻译成内核能听懂的机器指令。
Shell 有很多种流派,比如老牌稳定的
bash、界面酷炫功能强大的zsh(Mac默认)、经典的sh等。SHELL环境变量记录的就是当前正在为你服务的"翻译官"到底是谁、它住在哪个路径下。 -
如何查看它?
在终端中输入以下命令:
bashecho $SHELL
三、 获取环境变量的方法
在了解了什么是环境变量以及常见变量后,我们如何获取并使用它们呢?通常可以分为命令行操作获取 与代码层级获取两种流派。
1. 命令行操作获取
如果你在终端环境下工作,操作系统提供了非常直观的命令来管理和查看环境变量:
-
env:查看当前用户的所有环境变量列表。

-
export:不仅能查看,还可以用来导入新的环境变量。例如:export MY_VAL="hello"。

-
echo $XXX:查看某个特定环境变量的值(注意变量名前面必须加$符号)。

-
set: 显示本地定义的shell变量和环境变量

-
unset:清除指定的某个环境变量。例如:unset MY_VAL。

根据图片我们可以看到,通过unset删除MYENV后,就无法通过set找到了。
2. 代码层级获取(C/C++ 核心方法)
对于开发者而言,如何在编写的程序中获取系统里的环境变量呢?在 Linux 系统的 C/C++ 开发中,主要有以下几种经典方法:
方法 1:通过系统的系统调用函数 getenv(最推荐、最常用)
这是最优雅、最灵活的一种获取方式,通过传入变量名即可拿到对应的指针。
- 代码示例:
cpp
#include <iostream>
#include <cstdlib> // getenv 的头文件
int main() {
// 获取环境变量 USER 的值
char* user = std::getenv("USER");
if (user != nullptr) {
std::cout << "当前登录用户为: " << user << std::endl;
} else {
std::cout << "未找到该环境变量" << std::endl;
}
return 0;
}
方法 2:使用全局变量 extern char environ
Linux 系统中内置了一个名为 environ 的全局指针数组,它指向系统的环境变量表。每一项都是一个以 \0 结尾的字符串,形式为 name=value。因为是第三方全局变量,使用前需要用 extern 关键字声明。
- 代码示例:
cpp
#include <iostream>
// 声明外部全局变量
extern char **environ;
int main() {
// 遍历打印出所有的环境变量
for (int i = 0; environ[i] != nullptr; i++) {
std::cout << environ[i] << std::endl;
}
return 0;
}
思考 2:实际应用场景------如何写一个"只允许我执行"的专属程序?
利用 getenv函数,我们可以轻松做一些有趣的权限控制。比如你写了一个程序,不想让别人乱动,就可以通过读取 USER 或 LOGNAME 环境变量来做校验:
- 代码示例:
cpp
char* current_user = std::getenv("USER");
if (current_user == nullptr || std::string(current_user) != "你的用户名") {
std::cout << "【权限拒绝】其他人一律不让执行!" << std::endl;
return 1; // 强行退出
}
// 后面才是真正的程序逻辑...
方法 3:通过 main 函数的第三个参数 envp 获取
这也是操作系统非常底层且巧妙的设计。很多人以为 main 函数最多只有两个参数(argc 和 argv),但实际上它还支持第三个隐藏参数------envp,它同样是一个字符指针数组,专门用来接收父进程传过来的环境变量。
- 代码示例:
cpp
#include <iostream>
// main 函数的完整体形态
int main(int argc, char *argv[], char *envp[]) {
// 遍历打印出所有的环境变量
for (int i = 0; envp[i] != nullptr; i++) {
std::cout << envp[i] << std::endl;
}
return 0;
}
四、 环境变量的组织方式
在深入了解了获取环境变量的各种代码方法后,我们有必要从操作系统的内存底层,看一看这些环境变量到底是在内存中如何被组织和存储的。
1. 核心底层结构:字符指针数组(环境表)
如上图所示,每个程序(进程)启动时,操作系统都会为其分配并传入一张环境表(Environment Table)。
- 本质形态 :环境表本质上是一个字符指针数组 (即
char* [])。 - 存储逻辑 :
- 数组中的每一个元素都是一个一维指针,它们分别指向一个以空字符
\0结尾的字符串。 - 这些字符串的固定格式为:
键=值\0(例如HOME=/home/akaedu\0)。
- 数组中的每一个元素都是一个一维指针,它们分别指向一个以空字符
- 终止标志(NULL) :
由于环境变量的数量不是固定的(不同系统、不同用户各不相同),为了让程序知道这张表什么时候到头,操作系统的设计者采用了一个非常经典的技巧------在数组的最后放一个NULL(空指针)作为结束标记 。这也是为什么我们在使用environ或envp遍历环境表时,循环终止条件可以统一写成environ[i] != nullptr。
2. 内存布局透视:environ 指针的工作原理
结合我们在第三部分提到的 方法 2 (extern char **environ),我们可以完美对接上图的内存指向关系:
environ是什么?
它是一个"二级指针"(char **),用来指向环境表这个二级结构的头部。- 数据流向 :
environ指向字符指针数组的第一个元素位置。- 数组的第一个元素又指向了第一个环境变量字符串
"HOME=/home/akaedu"的内存首地址。 - 随着指针的自增(
environ++或environ[i]),程序依次向后读取,直到碰到底部的NULL元素,整个读取流程完美闭环。
💡 总结:两张表看懂进程的诞生
当你通过终端(bash)运行你的程序时,bash 实际上为你做好了两张极为关键的表并塞给了你的程序:
- 命令行参数表(
argv) :也是一个指针数组,最后以NULL结尾,里面存的是你在终端敲下的参数(如-a、-m)。 - 环境表(
environ) :同样是以NULL结尾的指针数组,里面完整克隆了父进程传递过来的系统级全局配置(如PATH、SHELL、USER)。
正因为有了这样的底层组织结构,我们的程序才既能"听懂"我们当下的临时指挥(参数),又能深刻"了解"它所生存的系统世界(环境)。
五、 环境变量的全局性与进程继承机制
通过前面的学习,我们知道可以通过代码获取环境变量。但你有没有深挖过:为什么我们在终端导出的变量,自己写的程序运行起来就能直接读取到?
接下来我们通过一个实验和底层原理解析,彻底搞懂环境变量的全局特性。
1. 经典实验:验证环境变量的继承
我们编写一个简单的 C 语言程序 test.c,用来获取一个名为 MYENV 的自定义环境变量:
c
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 尝试获取自定义环境变量 MYENV
char *env = getenv("MYENV");
if(env) {
printf("%s\n", env);
}
return 0;
}
实验步骤与现象:
-
直接运行程序:
在终端编译并直接运行该程序,发现什么都没有输出。这说明此时系统中根本不存在 MYENV 这个环境变量。

-
导出环境变量:
在终端输入以下命令,通过 export 导出该变量:
export MYENV='hello world!'
-
再次运行程序:
重新执行程序,发现终端成功打印出了:hello world!

2.核心原理:为什么程序能拿到这个变量?
这个实验现象背后,隐藏着 Linux 极其重要的进程派生与继承机制。
(1) 谁是父进程?
当我们在 Linux 中打开终端时,操作系统会为我们运行一个命令行解释器进程------bash。
我们在终端敲下的 export MYENV="hello world!",实际上是将这个环境变量注册到了 bash 进程的内部环境表中。
(2) 谁是子进程?
当我们在终端输入 ./mytest运行我们写好的程序时,bash 会通过系统调用 fork() 创建出一个全新的子进程来运行这个程序。
(3) 继承的本质
在 Linux 的设计中:子进程在创建时,会自动继承父进程的环境表!
bash(父进程)把自己的环境变量表完整地复制了一份,塞给了我们的程序(子进程)。
我们的程序内部调用 getenv("MYENV") 时,其实是在它自己已经继承过来的环境表里进行查找。
这就是为什么 export 后程序就能拿到数据,因为环境变量具有全局属性,可以被子进程一路继承下去。
💡 深度思考:本地变量 vs 环境变量
如果在终端不加 export,直接输入 MYENV="hello world" 会发生什么?
本地变量 :不加 export 定义的变量叫本地变量。它只在当前的 bash 进程内部有效,不会被子进程继承。如果此时运行程序,依然拿不到数据。
环境变量 :加上 export 的本质,就是把一个普通的本地变量"提升"为环境变量,赋予它全局且可被继承的超能力。
💡 总结
今天我们一起从基本概念 出发,一路向下推导到了操作系统的底层内存模型 和进程继承机制。
其实无论是每天都在用的 PATH 变量,还是代码里的 getenv 函数,理解了它们背后 "父进程搭台,子进程唱戏" 的继承逻辑,你在 Linux 环境下的开发就会变得如鱼得水!
💬 互动话题
最后留给大家一个小思考:
既然子进程会完美继承父进程的环境变量,那么我们在子进程(自己写的可执行程序)中用代码修改了一个环境变量,会影响到外面的父进程(终端 bash)吗? 欢迎在评论区留下你的看法和实验结果!如果有用的话,别忘了点赞、收藏、关注三连哦!我们下期见!