在Linux系统的广阔天地中,环境变量如同无形的神经网络,默默构建着程序运行的生态基础。它们以全局属性的特质贯穿整个进程体系,通过精妙的继承机制将配置信息从父进程传递到子进程,形成一张覆盖所有应用的环境网络。理解环境变量的工作原理,就如同掌握了一把开启系统运行机制的钥匙,让我们能够深入探索命令行背后的奥秘,解密程序执行的环境依赖,从而真正驾驭操作系统的核心力量。
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,环境变量通常具有某些特殊作用,同时它还具有全局属性。
例如:我们在编写c/c++代码的时候,在进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是依旧可以链接成功,生成可执行程序,原因就是有相关的环境变量帮助编译器进行查找
PATH : 指定命令的搜索路径。HOME :指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL : 当前Shell,它的值通常是/bin/bash。echo &NAME:NAME是我们需要查看发环境变量名。

env: 查看所有环境变量。

为什么执行ls命令的时候不用带./就可以执行,而我们自己生成的可执行程序必须要在前面带上./才可以执行?

而系统就是通过环境变量PATH来找到ls命令的,查看环境变量PATH我们可以看到如下内容:

PATH是linux系统的指令默认搜索路径,即当我们在bash命令行中输入指令的时候,linux会首先在这个PATH中使用冒号:分隔的路径下逐个进行查找这个指令对应的可执行程序,如果找到了这个指令对应的可执行程序,那么linux就会去执行这个指令,如果没有找到那么会进行报错 而ls命令实际就位于PATH当中的某一个路径下(
/usr/bin),所以就算ls命令不带路径执行,系统也是能够找到的。
那么我们有没有什么办法能够让自己的程序不带路径也能执行呢?——我们通过两种方法实现方式一:将可执行程序拷贝到环境变量PATH的某一路径下。
sudo cp 文件名 /usr/bin

方式二:将可执行程序所在的目录导入到环境变量PATH当中。
PATH=$PATH:(pwd查看的当前路径)

普通用户和超级用户:

命令 | 语法 | 作用 | 示例 | 作用范围 |
|---|---|---|---|---|
显示变量 | echo $VAR | 显示环境变量的值 | echo $PATH | 当前shell |
printenv | 显示所有环境变量 | printenv | 当前shell | |
printenv VAR | 显示指定环境变量 | printenv HOME | 当前shell | |
env | 显示所有环境变量 | env | 当前shell | |
设置变量 | VAR=value | 设置局部变量 | NAME="John" | 当前shell |
export VAR=value | 设置环境变量 | export PATH=$PATH:/bin | 当前shell及子进程 | |
修改变量 | export VAR=new_value | 修改环境变量值 | export PATH=/new/path | 当前shell及子进程 |
VAR=new_value | 修改局部变量值 | NAME="Jane" | 当前shell | |
删除变量 | unset VAR | 删除环境变量 | unset TEMP_DIR | 当前shell |
变量扩展 | ${VAR} | 变量值扩展 | echo ${PATH} | 当前shell |
${VAR:-default} | 空时使用默认值 | echo ${VAR:-"default"} | 当前shell | |
${VAR:=default} | 空时设置默认值 | echo ${VAR:="default"} | 当前shell | |
查看命令路径 | which cmd | 显示命令的完整路径 | which ls | 当前shell |
whereis cmd | 显示命令路径及手册页 | whereis python | 当前shell | |
type cmd | 显示命令类型 | type cd | 当前shell | |
特殊变量 | $HOME | 用户家目录 | echo $HOME | 所有shell |
$PATH | 命令搜索路径 | echo $PATH | 所有shell | |
$PWD | 当前工作目录 | echo $PWD | 当前shell | |
$USER | 当前用户名 | echo $USER | 所有shell | |
$SHELL | 当前shell路径 | echo $SHELL | 所有shell | |
$PS1 | 主提示符设置 | echo $PS1 | 当前shell | |
持久化设置 | 编辑 ~/.bashrc | 用户级环境变量 | vim ~/.bashrc | 永久生效 |
编辑 ~/.bash_profile | 登录shell环境变量 | vim ~/.bash_profile | 永久生效 | |
编辑 ~/.profile | 用户环境配置 | vim ~/.profile | 永久生效 | |
编辑 /etc/profile | 系统级环境变量 | sudo vim /etc/profile | 所有用户 | |
编辑 /etc/bashrc | 系统级bash配置 | sudo vim /etc/bashrc | 所有用户 | |
生效配置 | source file | 使配置文件立即生效 | source ~/.bashrc | 当前shell |
. file | 使配置文件立即生效 | . ~/.bashrc | 当前shell | |
进程环境 | export -p | 显示所有导出的环境变量 | export -p | 当前shell |
declare -x | 显示导出的环境变量 | declare -x | 当前shell | |
set | 显示所有变量和函数 | set | 当前shell | |
数组变量 | VAR=(val1 val2) | 定义数组变量 | FILES=(*.txt) | 当前shell |
echo ${VAR[@]} | 显示数组所有元素 | echo ${FILES[@]} | 当前shell | |
echo ${#VAR[@]} | 显示数组元素个数 | echo ${#FILES[@]} | 当前shell |
main 函数是 C/C++ 程序的入口点,它通过命令行参数机制为程序提供了与外部环境交互的重要接口。其标准形式为:
int main(int argc, char* argv[])参数解析:
argc(参数计数):表示命令行参数的数量,至少为 1(程序名称本身)argv(参数向量):字符指针数组,按顺序存储每个命令行参数字符串的起始地址底层调用机制:
在程序启动时,main 函数由运行时库的初始化代码(如 Startup() 或 CRTStartup())调用。这些启动例程负责解析命令行输入,并将处理后的参数传递给 main 函数。
参数传递原理: 当在 Linux bash 中执行命令时,系统会将空格分隔的各个字符串解析为独立参数。例如:
./mycmd -a file.txt这个命令会产生三个参数:
argv[0] → "./mycmd"argv[1] → "-a"argv[2] → "file.txt"argc 的值为 3指令原型:
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
// 检查参数数量:程序需要恰好1个参数(不含程序名本身)
if (argc != 2) {
// 显示正确的使用格式提示
printf("用法: %s -[a|b|c]\n", argv[0]);
return 1; // 返回非零值表示执行失败
}
// 输出程序名和用户输入的参数,便于调试和显示
printf("%s[1]->%s ", argv[0], argv[1]);
// 根据不同的命令行选项执行相应功能
if (strcmp(argv[1], "-a") == 0) {
// 处理 -a 选项:执行功能1
printf("功能1\n");
} else if (strcmp(argv[1], "-b") == 0) {
// 处理 -b 选项:执行功能2
printf("功能2\n");
} else if (strcmp(argv[1], "-c") == 0) {
// 处理 -c 选项:执行功能3
printf("功能3\n");
} else {
// 处理未知选项:提示用户输入错误
printf("未知选项\n");
return 1; // 返回非零值表示执行失败
}
// 程序正常执行完成
return 0;
}
argv 是程序与命令行环境交互的核心数据结构。这个指针数组以特定的方式组织命令行参数,并在末尾添加了一个重要的结束标记。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
int cnt = 0;
while (argv[cnt] != NULL)
{
printf("%s[%d]->%s\n", argv[0], cnt, argv[cnt]);
cnt++;
}
return 0;
}
char* env[]是main函数的第三个参数,用于接收环境变量表。
env 是一个以 NULL 结尾的字符串指针数组argv 类似,可以通过循环遍历直到遇到 NULL 指针打印出环境变量表:
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++)
{
printf("%s\n", env[i]);
}
return 0;
}
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
} r
eturn 0;
}libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头⽂件中,所以在使⽤时要用extern声明。
使用代码调用系统调用接口getenv获取PATH环境变量的值和当前登录用户
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("PATH:%s\n",getenv("USER"));
printf("USER:%s\n",getenv("USER"));
return 0;
}


set 命令显示所有变量后,配合 grep 进行过滤,可以快速定位到特定的本地变量 MYENV。具体命令为:set | grep MYENV。


结果:运行程序,访问出现错误,子进程中的环境变量中并没有
MYENV,是由于无法我们定义的本地变量MYENV由于不是环境变量所以没有被继承。
export将bash中的本地变量MYENVE变成环境变量,再使用env | grep MYENV的方式在bash的环境变量中找到MYENV。

如图此时我们运行自己的程序成功找到MYENV,即子进程继承了fork的环境变量,证明环境变量具有全局属性。

在 Shell 环境中,命令分为两大类型:常规命令和内建命令,它们在执行机制上有着本质的区别。
外部命令或磁盘命令,这类命令的本质是独立的可执行程序文件。当我们在 Shell 中输入一个常规命令时,系统会启动一个新的子进程来执行该命令。Shell 首先通过 PATH 环境变量指定的目录路径来查找对应的可执行文件,找到后使用 fork() 创建子进程,然后通过 exec() 系列函数加载并执行该程序。常见的如 ls、grep、find 等都属于常规命令,它们以二进制文件或脚本的形式存储在文件系统的特定目录中。每个常规命令的执行都会产生一个新的进程,执行完毕后该进程终止,控制权返回给 Shell 父进程。
常见的内建命令包括 cd、export、unset、echo 等,这些命令通常用于改变 Shell 自身的状态或环境。例如 cd 命令需要改变当前 Shell 的工作目录,如果作为外部命令实现,子进程改变自己的工作目录后无法影响父进程 Shell 的当前目录。
理解这两种命令的区别对于深入掌握 Shell 编程和系统管理具有重要意义,特别是在编写脚本时能够根据需求选择合适的命令类型。
环境变量是操作系统中具有全局属性的运行参数,通过进程继承机制在整个系统中传播。bash作为所有用户进程的父进程,其环境变量会被所有子进程继承,从而实现全局效应。环境变量与仅当前shell有效的本地变量不同,可通过export命令提升为环境变量。系统通过PATH环境变量查找命令,解释了为何系统命令无需路径而自定义程序需要。程序可通过main函数参数、getenv函数或environ全局变量获取环境变量。Shell命令分常规命令(外部可执行文件)和内建命令(Shell内置功能),后者直接修改Shell状态且效率更高。