在终端中直接输入ls就能执行命令,而当前目录中的程序通常需要写成./env_demo。程序也能读取HOME、PATH等信息,而不必每次都通过命令行参数传入。
这些现象与环境变量有关。本文整理环境变量的表示、PATH 查找、Shell 变量与 export,以及 C 程序读取环境的方法;虚拟内存和地址空间留到后续讨论。
一、环境变量是什么
环境变量是进程运行环境中的一组名称与值,常见表示形式为:
名称=值 HOME=/home/learner PATH=/usr/local/bin:/usr/bin:/bin它们向程序提供配置和运行背景。不同程序关注的变量不同,不是设置任意名称后,所有程序就会自动产生相应行为。
环境属于具体进程。父进程可以把环境传给新启动的程序,但这不意味着所有进程实时共享同一张环境变量表。
二、命令行参数与环境变量
二者都能把信息传给程序,但用途和传递方式不同。
./env_demo-ahello这次启动的参数可以通过argc、argv读取:
intmain(intargc,char*argv[]){for(inti=0;i<argc;++i)printf("argv[%d]=%s\n",i,argv[i]);return0;}此处是函数片段,使用时需要包含<stdio.h>。对于上面的命令,通常有:
argc=3 argv[0]=./env_demo argv[1]=-a argv[2]=helloargv[argc]是空指针。-a的功能不是 main 自动赋予的,而要由程序解析并实现。
环境变量则是另一组输入,例如程序通过getenv("HOME")获取目录配置,无需把 HOME 的值每次写成位置参数。
Linux 中还常见带第三个环境参数的 main 写法,但它不是 ISO C 通用的 main 形式。本篇使用标准的 main 形式,配合 getenv 或 Linux/POSIX 环境中的 environ,不将“main 最多有三个参数”作为通用语言规则。
三、PATH:不写完整路径时,去哪里找程序
查看当前 Shell 的 PATH:
echo"$PATH"PATH 是由冒号分隔的目录列表。假设它是:
/usr/local/bin:/usr/bin:/bin当 Bash 需要查找一个不带/的外部命令时,会利用 PATH。忽略命令缓存等细节后,搜索顺序可以用下面的图理解:
图只描述外部命令的 PATH 搜索。别名、函数和 Shell 内建命令等有自己的处理规则,不能把每条 Shell 命令都当作磁盘上的独立程序。
为什么要写 ./env_demo
./env_demo已经包含/,表示使用当前目录下的指定路径,不再依靠 PATH 搜索这个名字。
如果当前目录不在 PATH 中,只输入env_demo,即使文件就在眼前,也可能提示命令未找到。找到了文件,还需要满足执行权限等条件;PATH 不会绕过权限检查。
不建议为了省略./就把.放进 PATH,尤其不要放在搜索列表前面,以免在不可信目录中误执行同名程序。
若确实需要加入自己的工具目录,可保留原有 PATH 并追加:
exportPATH="$PATH:$HOME/bin"前提是这个目录中确实有需要运行、且具有执行权限的程序。不要直接覆盖原有 PATH,否则可能使常用外部命令无法按名字找到。
四、常见变量与查看方式
| 名称 | 常见用途 | 注意事项 |
|---|---|---|
PATH | 外部命令的搜索目录 | 不等于动态库搜索路径 |
HOME | 用户主目录 | 与当前工作目录不同 |
PWD | Shell 维护的当前工作目录信息 | 程序需要可靠获取工作目录时可使用 getcwd |
USER | 用户名相关信息 | 不能单凭它进行可靠的身份认证 |
SHELL | 通常记录用户的登录 Shell 路径 | 不保证就是此刻执行命令的 Shell |
查看单个值:
echo"$HOME"echo"$PWD"查看传给外部程序的环境:
env实际输出与用户和会话有关。环境中可能包含令牌、代理地址等敏感信息,不宜把完整 env 输出原样发布到博客。
五、Shell 变量与 export
在一个新的 Bash 终端中,用一个未使用的练习变量名操作:
LEARN_ENV_DEMO=helloecho"$LEARN_ENV_DEMO"env|grep'^LEARN_ENV_DEMO='赋值时等号两边不要加空格。普通 Shell 变量可以由当前 Shell 使用,但在没有导出属性时,不会作为环境变量传给普通外部程序。
接着导出:
exportLEARN_ENV_DEMOenv|grep'^LEARN_ENV_DEMO='此时新启动的外部程序可以收到这项环境信息。也可以把赋值与导出合在一起:
exportLEARN_ENV_DEMO=hello删除练习变量:
unsetLEARN_ENV_DEMOexport 是 Shell 内建命令,修改的是当前 Shell 的变量导出属性,不是创建一张所有进程共享的全局表。
六、环境怎样传给子进程
这是一张传递关系图,不表示所有程序共享同一块环境内存。fork 时子进程继承环境;执行新程序时,环境由启动方提供,通常包含导出的变量,也可以被显式增删。
父 Shell 后来修改变量,不会自动同步给已经运行的子进程;子程序修改自己的环境,也不会反向改掉父 Shell 的变量。
因此,有时所说的环境变量“全局性”,应理解为它可以沿进程启动关系被继承和使用,而不是全系统实时共享。
七、C 程序读取环境
1. getenv:按名称读取
保存为env_demo.c:
#include<stdio.h>#include<stdlib.h>intmain(void){constchar*names[]={"HOME","PATH","LEARN_ENV_DEMO"};for(size_ti=0;i<sizeof(names)/sizeof(names[0]);++i){constchar*value=getenv(names[i]);printf("%s=%s\n",names[i],value?value:"(not set)");}return0;}编译、运行:
gcc-Wall-Wextraenv_demo.c-oenv_demo ./env_demoexportLEARN_ENV_DEMO=hello ./env_demounsetLEARN_ENV_DEMO若练习变量原先未设置,第一次运行预期显示(not set),导出后重新启动程序则可以读到 hello。这是代码分析得到的预期现象,不是实际运行日志。
getenv找不到名称时返回 NULL;空字符串则表示变量存在但值为空,二者不能混为一谈。不要直接把可能为 NULL 的返回值交给%s。接口细节见 getenv 手册。
还可以只给这一次程序启动提供变量:
LEARN_ENV_DEMO=once ./env_demo这种写法不把这个临时值永久留在当前 Shell 中。
2. environ:遍历环境
在 Linux/POSIX 环境中,可以通过 environ 遍历环境字符串:
#include<stdio.h>externchar**environ;intmain(void){for(char**entry=environ;*entry!=NULL;++entry)puts(*entry);return0;}每个元素指向一条名称=值字符串,指针数组以 NULL 结束。调试时可以在自己的终端观察,但不要把完整输出直接公开。
USER 等变量由运行环境提供,也可能被人为修改。读取用户名作演示可以,不能把字符串比较当作真正的权限控制。
八、临时生效与配置文件
终端里的赋值和 export 默认只影响当前 Shell 及其后续启动的程序,不会自动写入配置文件。
若需要在后续会话中设置变量,Bash 常见的相关文件包括~/.bashrc和~/.bash_profile,但它们分别何时读取,与登录方式、是否交互及发行版配置有关,不能把两者当成无条件同时执行的文件。
本篇练习不要求修改系统配置,也不需要 root。先掌握临时设置、读取、继承和 unset,再按实际需求决定是否持久化。
九、复习要点
- argv 接收命令行参数,环境变量提供另一组运行配置。
- PATH 控制不带路径的外部命令搜索;
./程序指明当前目录下的路径。 - Shell 变量不一定被导出,export 决定它是否进入后续程序的环境。
- 环境可以继承,但不是父子进程间实时同步的共享表。
- getenv 按名读取,environ 可以遍历;读取时注意 NULL 和敏感信息。
- 本次设置与持久化配置是两回事,不能把 USER 当作可靠认证依据。