【Linux系统】命令行参数和环境变量
1. 环境变量基本概念
-
定义:环境变量(Environment Variables)是操作系统中用于指定系统运行环境参数的变量,通常以键值对(变量名+内容)形式存在。它们为程序提供运行所需的配置信息,如文件路径、系统行为设置等 。
- 示例:在C/C++编译时,链接器通过环境变量(如
LIBRARY_PATH
)自动定位动态/静态库的位置,无需手动指定路径即可成功生成可执行程序 。
- 示例:在C/C++编译时,链接器通过环境变量(如
2. 命令行参数
在环境变量展开介绍前,我们先来认识一下命令行参数,那命令行参数又是什么呢?
命令行参数则是用户在启动程序时传递给程序的参数,这些参数在程序运行时可用。它们通常是特定于程序的,用于指定程序运行时的一些选项或参数。
例如我们常写的main函数,他其实是可以有参数的
int main(int argc, char *argv[]) {// ...
}
-
int argc
:参数个数,包括程序名。 -
char *argv[]
:参数值数组,argv[0]
是程序名,后续元素是用户传递的参数。
argv
是一个以NULL
结尾的指针数组,结构如下:
char *argv[] = { "./a.out", "arg1", "arg2", NULL }; // 示例
argv[0]
:程序名称(如"./a.out"
)。argv[1..argc-1]
:用户输入的参数。argv[argc] = NULL
:标识参数数组结束 。
示例代码解释
以下是一个简单的示例,展示如何使用命令行参数:
#include <stdio.h>int main(int argc, char *argv[]) {for (int i = 0; i < argc; i++) {printf("argv[%d]: %s\n", i, argv[i]);}return 0;
}
代码解析:
-
argc
是命令行参数的个数。 -
argv
是一个指向字符数组的指针数组,每个元素对应一个命令行参数。 -
通过循环遍历
argv
数组,可以打印出所有的命令行参数。
运行示例:
假设将上述代码保存为 code.c
,编译并运行:
ltx@hcss-ecs-d90d:~/lesson4$ ls -l
total 8
-rw-rw-r-- 1 ltx ltx 766 Jul 15 15:18 code.c
-rw-rw-r-- 1 ltx ltx 58 Jul 15 15:21 Makefile
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
argv[0]: ./code
ltx@hcss-ecs-d90d:~/lesson4$ ./code a
argv[0]: ./code
argv[1]: a
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b
argv[0]: ./code
argv[1]: a
argv[2]: b
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c d
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
argv[4]: d
执行make
命令,根据Makefile
中的规则编译代码。这里调用了gcc
编译器,将code.c
文件编译成一个可执行文件code
。
从运行结果可以看出,程序正确地接收并输出了命令行参数。argv[0]
始终是程序本身的名称,后面的argv[1]
到argv[n]
依次是提供的命令行参数。参数的个数由argc
表示,在每次运行中,argc
的值等于输出的argv
索引最大值加1。
其实我们的进程在启动时默认就有一个命令行参数表
-
传递机制:父进程(如Shell)通过
exec()
系统调用将参数表复制到子进程的栈空间,子进程的main()
函数通过argc
(参数数量)和argv
(参数表指针)访问 。
实际应用场景
那命令行参数有什么用呢?
我们再来看一段代码:
#include <stdio.h>
#include <string.h>// main有参数吗?有int main(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s [-a|-b|-c]\n", argv[0]);return 1;}const char *arg = argv[1];if(strcmp(arg, "-a")==0)printf("这是功能1\n");else if(strcmp(arg, "-b")==0)printf("这是功能2\n");else if(strcmp(arg, "-c")==0)printf("这是功能3\n");elseprintf("Usage: %s [-a|-b|-c]\n", argv[0]);return 0;
}
运行结果:
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ ./code -a
这是功能1
ltx@hcss-ecs-d90d:~/lesson4$ ./code -b
这是功能2
ltx@hcss-ecs-d90d:~/lesson4$ ./code -c
这是功能3
ltx@hcss-ecs-d90d:~/lesson4$ ./code -abc
Usage: ./code [-a|-b|-c]
- 错误处理:
if(argc != 2)
确保用户必须提供一个选项。如果参数数量不符,打印用法并退出(返回非零值表示错误)。 - 选项匹配:
strcmp
比较argv[1]
与预定义字符串(如"-a"
),匹配成功则执行对应功能。这种设计允许程序通过不同选项激活不同子功能,体现了“单一程序,多种行为”的灵活模式。 - 鲁棒性设计:
else
分支处理无效选项(如用户输入-d
),防止程序崩溃。
此代码的核心在于命令行参数作为程序行为的控制开关,通过参数值动态决定执行路径。这是Linux系统工具(如ls
、grep
)的基础实现原理。
main函数命令行参数的功能与原理
命令行参数是操作系统与程序间传递配置信息的核心机制,尤其在Linux环境中,它实现了程序的模块化和可配置性。以下是其功能与原理的详细分析。
1. 核心功能:
- 行为控制:允许同一程序通过不同参数执行不同功能(如示例中的
-a
、-b
)。这是Linux指令(如ls -l
vsls -a
)多样性的基础。 - 输入传递:向程序传递运行时数据(如文件名、数值阈值)。例如,
grep "pattern" file.txt
中,"pattern"
和"file.txt"
通过argv
传递。 - 错误预防:通过
argc
检查参数数量,避免无效输入导致的未定义行为(如示例中的if(argc != 2)
)。 - 自文档化:
argv[0]
用于打印用法(如printf("Usage: %s ...", argv[0])
),提升用户体验。
2. 工作原理:
- Shell解析阶段:
- 用户输入命令(如
./program -a
)后,Shell(如Bash)按空格分割字符串,生成argv
数组:["", "-a", NULL]
。 - Shell通过
exec()
系统调用(如execv
)启动程序,并将argv
数组复制到进程内存栈中。
- 用户输入命令(如
- 程序执行阶段:
- 操作系统加载程序后,自动将
argc
和argv
传递给main
函数。 - 程序解析
argv
(如使用strcmp
或库函数如getopt
),执行对应逻辑。
- 操作系统加载程序后,自动将
- 内存结构:
argv
和environ
(环境变量表)在进程地址空间中相邻存储,均由父进程(Shell)
ls
是Linux核心命令,用于列出目录内容。其选项(如-l
、-a
)通过main
函数的命令行参数实现,本质与我们的示例代码相同。
所有Linux命令的选项本质都是通过main
的argv
实现
为什么平时写代码时main
函数没有参数?
-
简单性:大多数简单的程序不需要命令行参数。它们可能从标准输入读取数据,或者处理硬编码的文件名等。在这种情况下,使用无参数的
main
函数更为简洁和直接。 -
代码可读性:省略不必要的参数可以使代码更清晰,减少混乱。如果程序不需要处理命令行参数,那么包含
argc
和argv
只会增加不必要的复杂性。 -
默认行为:C标准允许
main
函数的这两种常见形式(带参数和不带参数)。编译器通常会支持这两种形式,所以开发者可以选择最适合自己需求的形式。 -
习惯和惯例:在教学和示例代码中,通常使用最简单的形式来避免分散学习者的注意力。这可能导致许多人在初学时习惯于使用无参数的
main
函数。
总之,是否使用带参数的main
函数取决于程序的具体需求。如果程序不需要命令行参数,使用无参数的main
函数是完全合理且常见的做法。
3. 一个例子,一个环境变量
3.1 示例
ltx@hcss-ecs-d90d:~/lesson4$ ls
code code.c Makefile
ltx@hcss-ecs-d90d:~/lesson4$ pwd
/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]
思考:为什么我们的代码编译成可执行程序后需要加路径 ./ (当前目录下),而系统的指令不需要带路径呢?
我们来试一下,如果我们的可执行程序不带路径会怎么样呢?
ltx@hcss-ecs-d90d:~/lesson4$ code
Command 'code' not found, but can be installed with:
snap install code
Please ask your administrator.
我们发现,系统说他找不到这个指令
那如果我们在使用系统的指令的时候带上路径会有什么不一样吗?
ltx@hcss-ecs-d90d:~/lesson4$ which ls
/usr/bin/ls
ltx@hcss-ecs-d90d:~/lesson4$ /usr/bin/ls
code code.c Makefile
ltx@hcss-ecs-d90d:~/lesson4$ ls
code code.c Makefile
可以看到,系统的指令带不带路径都能找到,我们的程序不带路径就找不到,这是为什么呢?
在Linux系统中,当你运行一个可执行程序时,系统会根据环境变量PATH
中定义的目录来查找该程序。而我们的可执行程序code
不在PATH
中定义的目录里,所以在运行时需要指定其相对路径./code
。否则,系统会在PATH
指定的目录中查找,找不到就会提示命令未找到。
3.2 环境变量PATH
环境变量PATH
是一个包含多个目录路径的变量,用于告诉操作系统在哪些目录中查找可执行文件。例如,当你在命令行中输入ls
时,系统会在PATH
指定的目录中依次查找ls
命令。
运行以下命令可以查看当前的PATH
环境变量:
echo $PATH
输出示例:
ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
为什么系统命令不需要带路径?
系统命令通常安装在PATH
中定义的目录里,如/usr/bin
、/bin
等。因此,当你运行ls
时,系统会在PATH
中的目录查找并找到/usr/bin/ls
。
为什么我们的程序不带路径就找不到?
我们的程序code
位于当前目录./
下,而./
通常不在PATH
环境变量中。因此,当你运行code
时,系统会在PATH
指定的目录中查找,但找不到,所以提示命令未找到。
为什么默认不包含当前目录?
- 安全风险:若
PATH
包含当前目录(.
),攻击者可在公共目录放置恶意程序(如伪造ls
),用户进入该目录后输入ls
可能触发恶意程序 。 - 命名冲突:当前目录下的程序名可能与系统指令冲突(如自定义
test
程序覆盖系统test
命令)。
如何让我们的程序不带路径就能运行?
有以下几种方法:
方法 | 命令示例 | 优点 | 缺点 |
---|---|---|---|
复制到系统路径 | sudo cp code /usr/local/bin | 永久生效,所有用户可用 | 需 root 权限,污染系统路径 |
修改 PATH 环境变量 | export PATH=$PATH:/home/ltx/lesson4 | 无需权限,快速生效 | 仅当前终端会话有效 |
永久生效配置 | 在 \~/.bashrc 添加:export PATH=$PATH:/home/ltx/lesson4 | 用户级持久化 | 需重启终端或执行 source \~/.bashrc |
示例:
ltx@hcss-ecs-d90d:~/lesson4$ export PATH=$PATH:/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ code
Usage: code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ code -a
这是功能1
为什么系统命令可以带路径运行?
系统命令的路径通常在PATH
中,因此无论是否带路径,系统都能找到它们。例如,/usr/bin/ls
和ls
都可以运行,因为/usr/bin
在PATH
中。
总结
-
系统通过环境变量
PATH
来查找命令。 -
我们的程序
code
不在PATH
中,因此需要带路径运行。 -
可以通过修改
PATH
或移动程序到PATH
中的目录来让程序不带路径运行。
3.3 环境变量的存储机制深度解析
环境变量的本质是操作系统与应用程序间的动态配置桥梁,其存储设计融合了内存管理、持久化策略和访问效率的平衡。
一、存储层级:从内存到磁盘的协同架构
环境变量的存储分为运行时内存存储和持久化磁盘存储两级,通过操作系统内核协同运作。
存储层级 | 物理载体 | 数据结构 | 生命周期 | 典型操作 |
---|---|---|---|---|
运行时存储 | 进程内存空间 | 指针数组(char **environ ) | 进程存活期间 | getenv() , setenv() |
持久化存储 | 磁盘配置文件(如.bashrc ) | 文本键值对 | 永久生效 | source \~/.bashrc |
-
运行时内存存储机制
-
数据结构:以
NULL
结尾的连续指针数组(char *envp[]
),每个指针指向KEY=VALUE
格式的字符串。// 内存布局示例 char *envp[] = { "PATH=/usr/bin", "HOME=/home/ltx", NULL // 结束标记 };
-
存储位置:位于进程用户空间栈的顶部,通过
task_struct->mm->env_start
定位(Linux内核)。 -
访问效率:O(n) 遍历复杂度,但通过哈希表缓存(如Bash的全局环境变量表)优化高频访问。
-
-
持久化磁盘存储机制
-
存储形式:纯文本键值对(如
export PATH=$PATH:/new/path
)。 -
加载过程:
-
嵌入式系统特例:U-Boot将环境变量存储在独立Flash扇区,包含CRC校验头和标志位:
struct env_data {uint32_t crc; // 校验码uint8_t flags; // 状态标志char data[4096]; // 键值对数据 };
-
二、数据结构:键值对的组织与优化策略
-
基本结构
- 键值编码:
KEY=VALUE
格式,禁止空格(如PATH=/usr/bin:/local/bin
)。 - 内存布局:连续字符串数组,通过
extern char **environ
全局变量访问。
- 键值编码:
-
性能优化技术
- Copy-on-Write:子进程继承父进程环境变量表时共享内存页,修改时复制新页。
- 哈希索引:Shell维护哈希表(如Bash的
env_hash
),将O(n)
查找降至O(1)
。 - 字符串池化:重复值复用同一内存地址(如多进程共享
PATH
值)。
三、进程继承:环境变量的传递与隔离
-
继承机制
fork()
时复制:子进程获得父进程环境变量表的只读副本,通过指针共享原始数据。exec()
时重建:新程序加载时,内核将环境变量表指针压入新进程栈顶,初始化environ
。
-
作用域隔离
setenv("TEMPVAR", "123"); // 修改当前进程环境 pid_t pid = fork(); if (pid == 0) {// 子进程 TEMPVAR 仍为原值setenv("TEMPVAR", "456"); // 仅修改子进程副本 }
4. 常见环境变量
以下是一些常见环境变量的详解:
1. PATH
-
作用:指定系统查找可执行文件的路径。
-
示例:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
-
说明:当在终端输入命令时,系统会在这些路径下查找对应的可执行文件。
2. HOME
-
作用:指示当前用户的主目录。
-
示例:
/home/username
-
说明:用户的主目录通常用于存储个人文件和配置文件。
3. SHELL
-
作用:指定当前用户使用的 shell 类型。
-
示例:
/bin/bash
-
说明:表明用户使用的是哪种 shell(如 Bash、Zsh 等)。
4. USER
-
作用:当前登录的用户名。
-
示例:
username
-
说明:用于标识当前用户。
5. LOGNAME
-
作用:当前登录的用户名。
-
示例:
username
-
说明:与 USER 类似,通常用于记录登录名称。
6. PWD
-
作用:当前工作目录。
-
示例:
/home/username/documents
-
说明:显示用户当前所在的目录。
7. oldPWD
-
作用:上一次工作目录。
-
示例:
/home/username/downloads
-
说明:用于记录用户之前所在的目录,方便用户返回。
8. LANG
-
作用:指定系统的语言和区域设置。
-
示例:
zh_CN.UTF-8
-
说明:影响系统显示的语言和字符编码。
9. TZ
-
作用:设置时区。
-
示例:
Asia/Shanghai
-
说明:影响系统显示的时间。
10. DISPLAY
-
作用:指定显示图形界面的显示器。
-
示例:
:0
-
说明:用于 X Window 系统,标识图形界面显示的设备。
11. EDITOR
-
作用:指定默认的文本编辑器。
-
示例:
vim
、nano
-
说明:用于在命令行中编辑文件。
12. PAGER
-
作用:指定默认的分页程序。
-
示例:
less
、more
-
说明:用于分页显示文件内容。
13. LD_LIBRARY_PATH
-
作用:指定动态链接库的搜索路径。
-
示例:
/usr/local/lib:/opt/myapp/lib
-
说明:系统会在这些路径下查找所需的动态链接库。
14. CLASSPATH
-
作用:指定 Java 类文件的搜索路径。
-
示例:
/.:/usr/lib/java:.
-
说明:用于 Java 开发,指定 Java 虚拟机查找类文件的路径。
15. HISTFILE
-
作用:指定命令历史记录文件的位置。
-
示例:
~/.bash_history
-
说明:用于记录用户在 shell 中执行过的命令。
16. HISTSIZE
-
作用:设置命令历史记录的大小。
-
示例:
1000
-
说明:定义可以保存的命令历史记录的数量。
17. MAIL
-
作用:指定用户的邮件文件位置。
-
示例:
/var/mail/username
-
说明:用于通知用户有新邮件。
18. TERM
-
作用:指定终端类型。
-
示例:
xterm
、linux
-
说明:影响终端模拟器的行为和显示效果。
19. SSH_AGENT_PID
-
作用:指定 ssh-agent 进程的 PID。
-
示例:
1234
-
说明:用于 SSH 密钥管理。
20. SSH_AUTH_SOCK
-
作用:指定 ssh-agent 的套接字文件位置。
-
示例:
/tmp/ssh-abc123/agent.1234
-
说明:用于 SSH 密钥认证。
21. VIRTUAL_ENV
-
作用:指定当前激活的 Python 虚拟环境路径。
-
示例:
/home/username/envs/myenv
-
说明:用于 Python 开发,隔离项目依赖。
22. XDG_CONFIG_HOME
-
作用:指定用户配置文件的目录。
-
示例:
~/.config
-
说明:用于存储应用程序的配置文件。
23. XDG_DATA_HOME
-
作用:指定用户数据文件的目录。
-
示例:
~/.local/share
-
说明:用于存储应用程序的数据文件。
24. XDG_CACHE_HOME
-
作用:指定用户缓存文件的目录。
-
示例:
~/.cache
-
说明:用于存储应用程序的缓存文件。
25. XDG_RUNTIME_DIR
-
作用:指定用户运行时文件的目录。
-
示例:
/run/user/1000
-
说明:用于存储用户运行时的临时文件。
26. XDG_DESKTOP_DIR
-
作用:指定用户的桌面目录。
-
示例:
~/.desktop
-
说明:用于存储桌面文件和快捷方式。
27. XDG_DOCUMENTS_DIR
-
作用:指定用户的文档目录。
-
示例:
~/documents
-
说明:用于存储用户的文档文件。
28. XDG_DOWNLOAD_DIR
-
作用:指定用户的下载目录。
-
示例:
~/downloads
-
说明:用于存储下载的文件。
29. XDG_MUSIC_DIR
-
作用:指定用户的音乐目录。
-
示例:
~/music
-
说明:用于存储音乐文件。
30. XDG_PICTURES_DIR
-
作用:指定用户的图片目录。
-
示例:
~/pictures
-
说明:用于存储图片文件。
31. XDG_VIDEOS_DIR
-
作用:指定用户的视频目录。
-
示例:
~/videos
-
说明:用于存储视频文件。
总结
环境变量在操作系统和应用程序中起着重要的作用。它们帮助配置运行时环境,指定资源位置,并影响程序的行为。了解这些常见环境变量的用途可以帮助用户更好地管理和使用系统资源,提高工作效率。
5. 环境变量操作命令
-
echo 命令
-
用途:显示某个环境变量的值。
-
示例:
echo $PATH
。这个命令会输出当前系统中环境变量PATH
的值,它包含了系统查找可执行文件的多个目录路径,这些路径之间用冒号分隔。
-
-
export 命令
-
用途:设置一个新的环境变量或修改现有环境变量的值,并将其导出到 shell 环境中,使其对当前 shell 会话及其子进程可见。
-
示例:
export MY_VAR="my_value"
。这会创建一个新的环境变量MY_VAR
,并将其值设置为"my_value"
。如果该变量已经存在,这个命令会修改其值。
-
-
env 命令
-
用途:显示所有当前环境变量。
-
示例:直接在终端输入
env
,然后回车,系统会列出当前 shell 环境中所有的环境变量及其对应的值,包括系统默认的环境变量和用户自定义的环境变量。
-
-
unset 命令
-
用途:清除环境变量,使其不再存在于当前 shell 环境中。
-
示例:
unset MY_VAR
。这个命令会清除之前设置的MY_VAR
环境变量,之后在当前 shell 会话中再使用echo $MY_VAR
就不会输出该变量的值了。
-
-
set 命令
-
用途:显示本地定义的 shell 变量和环境变量。
-
示例:在终端输入
set
,它会列出当前 shell 环境中所有的变量,包括环境变量和 shell 的局部变量。这些变量可能包括用户定义的变量、shell 内置变量以及环境变量等。
-
示例:
ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH #输出环境变量PATH的值
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ltx@hcss-ecs-d90d:~/lesson4$ export MY_VAR="my_value" #设置一个新的环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #显示所有当前环境变量
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
MY_VAR=my_value #我们刚才设置的
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env
ltx@hcss-ecs-d90d:~/lesson4$ unset MY_VAR #清除之前设置的 MY_VAR 环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #再次显示所有当前环境变量,发现刚才设置的MY_VAR环境变量清除了
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env
6. 环境变量的组织方式
一、数据结构:键值对与内存布局
-
核心数据结构
环境变量通过
KEY=VALUE
格式的字符串数组 组织,以NULL
指针作为结束标志:// C语言中环境表的存储结构 char *envp[] = { "HOME=/home/ltx", "PATH=/usr/bin:/bin", "LANG=en_US.UTF-8", NULL // 结束标识 };
- 内存布局特点:
- 连续内存块存储字符串指针
- 每个指针指向
KEY=VALUE
格式的独立字符串 - 数组末尾以
NULL
指针标识边界
- 内存布局特点:
-
系统级实现差异
系统 实现方式 访问接口 Linux char **environ
全局变量extern char**environ
Windows _environ
(CRT库)_getenv()
嵌入式系统 独立Flash扇区(带CRC校验) U-Boot的 env save
命令
二、进程级管理:继承与作用域控制
1. 进程启动时的传递机制
通过execve()
系统调用实现父子进程传递:
// Linux内核系统调用
int execve(const char *filename, char *const argv[], // 参数表char *const envp[] // 环境变量表
);
- 传递流程:
2. 作用域控制原理
变量类型 | 作用域 | 生命周期 | 底层机制 |
---|---|---|---|
环境变量 | 进程及所有子进程 | 进程会话期间 | 通过fork() 继承environ 指针 |
Shell变量 | 仅当前Shell进程 | Shell会话期间 | 存储于Shell进程堆内存 |
关键区别:环境变量通过export
命令将Shell变量写入environ
数组
3. 系统级环境变量
系统级环境变量是在系统范围内对所有用户和进程都有效的变量。这些变量通常在系统启动时由初始化脚本设置,存储在/etc/environment
、/etc/profile
或/etc/profile.d/
等文件中。例如:
-
PATH
:指定系统查找可执行文件的路径。 -
LANG
:指定系统的语言和区域设置。
4. 用户级环境变量
用户级环境变量是针对特定用户的变量,只对当前用户有效。这些变量通常在用户的 shell 配置文件中设置,如~/.bashrc
、~/.bash_profile
或~/.zshrc
等。例如:
-
EDITOR
:指定用户默认的文本编辑器。 -
SSH_AGENT_PID
:指定 ssh-agent 进程的 PID。
5. 进程级环境变量
进程级环境变量是进程在运行时继承自父进程的变量。当一个进程创建子进程时,子进程会继承父进程的环境变量。子进程可以修改这些变量,但这些修改只对子进程及其后续创建的子进程有效,不会影响父进程或其他进程。例如:
-
在终端中运行
export MY_VAR="my_value"
后,当前终端及其子进程都会继承这个变量。
6. 作用域
环境变量的作用域决定了它们的可见性和影响范围:
-
全局作用域:系统级环境变量,对所有用户和进程有效。
-
用户作用域:用户级环境变量,只对当前用户及其进程有效。
-
进程作用域:进程级环境变量,只对当前进程及其子进程有效。
三、通过代码如何获取环境变量
在这之前我们要在再来了解一个命令行参数,前文中我们知道了main函数可以有参数,那最多有几个参数呢?
其实main函数最多有三个命令行参数,第三个参数就是envp
1. 参数定义与类型
envp
(environment pointers)
-
类型:
char *envp[]
或char **envp
-
功能:接收环境变量表(Environment Variables Table),存储当前进程运行所需的环境配置信息。
-
示例
int main(int argc, char *argv[], char *envp[]) {for (int i = 0; envp[i] != NULL; i++) {printf("envp[%d]: %s\n", i, envp[i]);}return 0; }
运行结果:
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
envp[0]: SHELL=/bin/bash
envp[1]: HISTSIZE=1000
envp[2]: HISTTIMEFORMAT=%F %T ltx
envp[3]: PWD=/home/ltx/lesson4
envp[4]: LOGNAME=ltx
envp[5]: XDG_SESSION_TYPE=tty
envp[6]: MOTD_SHOWN=pam
envp[7]: HOME=/home/ltx
envp[8]: LANG=en_US.UTF-8
envp[9]: LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
envp[10]: SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
envp[11]: LESSCLOSE=/usr/bin/lesspipe %s %s
envp[12]: XDG_SESSION_CLASS=user
envp[13]: TERM=xterm
envp[14]: LESSOPEN=| /usr/bin/lesspipe %s
envp[15]: USER=ltx
envp[16]: DISPLAY=localhost:10.0
envp[17]: SHLVL=1
envp[18]: XDG_SESSION_ID=343
envp[19]: XDG_RUNTIME_DIR=/run/user/1000
envp[20]: SSH_CLIENT=59.62.147.11 3278 22
envp[21]: XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
envp[22]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
envp[23]: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
envp[24]: SSH_TTY=/dev/pts/0
envp[25]: OLDPWD=/home/ltx
envp[26]: _=./code
2. 与全局变量environ
的关系
-
等价性:
envp
与全局变量extern char **environ
指向同一内存地址,两种访问方式等价:// 方式1:通过main参数 int main(int argc, char *argv[], char *envp[]) {for (int i=0; envp[i]!=NULL; i++) printf("%s\n", envp[i]); }// 方式2:通过全局变量 #include <stdio.h> extern char **environ; int main() {for (int i=0; environ[i]!=NULL; i++) printf("%s\n", environ[i]); }
-
实现差异:
environ
由C运行时库(如glibc)定义,envp
由操作系统内核通过execve()
系统调用传递
四、技术原理:操作系统如何传递环境变量
1. 进程创建时的传递机制
步骤 | 说明 | 底层实现 |
---|---|---|
1. Shell预处理 | 解析用户命令,合并继承的环境变量与临时设置(如VAR=value command ) | Bash通过env_hash 表管理变量 |
2. 调用execve() | Shell通过系统调用加载程序,传递环境表:int execve(path, argv, envp) | Linux内核复制envp 到新进程栈顶 |
3. 程序启动初始化 | C运行时库(crt0)从栈中读取envp ,初始化全局变量environ | crt0.o 代码片段:environ = __envp; |
4. 传递给main() | 若main 声明包含envp 参数,则将其地址压栈 | x86_64调用约定:rdi=argc, rsi=argv, rdx=envp (什么是Python,它的用途是什么?-腾讯云开发者社区-腾讯云) |
2. 环境变量表的继承规则
- 默认继承:子进程自动继承父进程环境变量表(如从Shell启动的程序继承
PATH
、HOME
等)。 - 动态修改:父进程可通过
exec
族函数指定新环境表(如execle("/bin/ls", "ls", NULL, new_envp)
),覆盖默认继承
五、拓展
程序入口点 _start
:从操作系统到用户代码的桥梁
一、_start
的本质与定位
-
程序入口点的真实身份
- 传统认知中,
main
函数被视为程序的起点,但实际执行流程中,_start
才是操作系统加载程序后执行的第一条指令。这一机制由操作系统与编译器共同约定,确保程序初始化逻辑的统一性 - 技术定义:
_start
是一个由汇编实现的符号(Symbol),通常由链接器(如 GNU ld)通过默认链接脚本指定。其内存地址被写入可执行文件的ELF头部(e_entry
字段),操作系统内核加载程序时直接跳转至该地址
- 传统认知中,
-
与
main
函数的层级关系关键结论:
main
仅是用户代码的语法入口,而_start
是操作系统层面的实际入口点_start
与main
的关系-
_start
是main
的前置调用:_start
先于main
执行,完成必要的初始化工作后才调用main
。 -
main
是_start
的一部分:从逻辑上讲,main
是程序逻辑的起点,而_start
是程序执行的真正起点。
-
二、_start
的底层实现机制
-
初始化操作的核心步骤
_start
函数需完成硬件环境的基础配置,具体包括:- 栈指针(SP)设置:为函数调用栈分配内存,通常指向进程地址空间的高地址区域(如 Linux 用户栈起始于
0x7fffffff0000
) - 寄存器清零:清除通用寄存器中的随机值,避免干扰后续逻辑(如 x86 的
xor ebp, ebp
) - 参数传递:从内核传递的栈中提取
argc
和argv
,为调用__libc_start_main
做准备:; x86_64 架构示例(AT&T语法) _start:xor %rbp, %rbp ; 清空帧指针mov (%rsp), %rdi ; argc → rdilea 8(%rsp), %rsi ; argv → rsicall __libc_start_main ; 调用C库初始化函数
- 栈指针(SP)设置:为函数调用栈分配内存,通常指向进程地址空间的高地址区域(如 Linux 用户栈起始于
-
跳转至C运行时库
__libc_start_main
(Glibc实现)是_start
的核心调用对象,其职责包括:功能 实现细节 影响范围 全局变量初始化 执行 .data
段赋值与.bss
段清零静态存储期变量 C++全局对象构造 调用 _init()
和__do_global_ctors_aux
C++跨平台兼容性 线程局部存储(TLS) 设置线程局部变量模板 多线程程序基础 安全机制启动 栈保护(Stack Guard)与地址随机化(ASLR)校验 防御内存攻击 环境变量处理 解析 envp
并设置environ
全局变量进程环境配置 完成初始化后,该函数最终调用
main(argc, argv, envp)
,并将返回值传递给exit()
6. 通过系统调用获取或设置环境变量
getenv
函数
getenv
是一个标准库函数,用于获取环境变量的值。
函数原型
#include <stdlib.h>
char *getenv(const char *name);
参数
-
name
:要获取值的环境变量的名称。
返回值
-
如果环境变量存在,则返回一个指向其值的字符串指针。
-
如果环境变量不存在,则返回
NULL
。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {char *path = getenv("PATH");if (path != NULL) {printf("PATH: %s\n", path);} else {printf("PATH environment variable not found.\n");}char *home = getenv("HOME");if (home != NULL) {printf("HOME: %s\n", home);} else {printf("HOME environment variable not found.\n");}char *nonexistent = getenv("NONEXISTENT_VAR");if (nonexistent == NULL) {printf("NONEXISTENT_VAR not found.\n");}return 0;
}
输出示例
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
HOME: /home/ltx
NONEXISTENT_VAR not found.
putenv
函数
putenv
是一个标准库函数,用于设置或修改环境变量。
函数原型
#include <stdlib.h>
int putenv(char *string);
参数
-
string
:一个形如"NAME=VALUE"
的字符串,表示要设置的环境变量名称和值。
返回值
-
如果成功,则返回
0
。 -
如果失败,则返回非零值。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {// 设置新的环境变量char *new_var = "MY_VAR=my_value";if (putenv(new_var) == 0) {printf("Set MY_VAR successfully.\n");} else {perror("Failed to set MY_VAR");}// 修改现有环境变量char *new_path = "PATH=/new/path";if (putenv(new_path) == 0) {printf("Modified PATH successfully.\n");} else {perror("Failed to modify PATH");}// 获取并打印环境变量char *my_var = getenv("MY_VAR");if (my_var != NULL) {printf("MY_VAR: %s\n", my_var);}char *path = getenv("PATH");if (path != NULL) {printf("PATH: %s\n", path);}return 0;
}
输出示例
Set MY_VAR successfully.
Modified PATH successfully.
MY_VAR: my_value
PATH: /new/path
注意事项
-
putenv
的参数:-
参数字符串
string
必须是形如"NAME=VALUE"
的格式。 -
如果
NAME
已经存在,则其值会被更新为VALUE
。 -
如果
NAME
不存在,则会创建新的环境变量。
-
-
内存管理:
-
putenv
会将参数字符串string
保存在环境变量中,因此该字符串必须在调用后保持有效。通常,建议使用静态分配或动态分配的内存来存储参数字符串。
-
-
线程安全性:
-
getenv
和putenv
在多线程环境中是线程安全的,但需要注意在修改环境变量时的并发问题。
-
-
替代函数:
-
在一些系统中,还有其他函数如
setenv
和unsetenv
,它们提供了更灵活的接口来设置和删除环境变量。这些函数的使用方式与putenv
类似,但提供了更明确的参数分离。
-
总结
-
getenv
:用于获取环境变量的值。 -
putenv
:用于设置或修改环境变量。
这两个函数是操作环境变量的标准库函数,提供了简单易用的接口。在实际应用中,可以根据需要使用这些函数来获取和设置环境变量,以适应不同的运行环境和需求。