5. 环境变量
目录
2.2. 真实的 main 函数样子 --- main(int argc,char* argv[])
4.6.2. export 的作用:将本地变量提升为环境变量
4.6.3. 关键困惑:export 是子进程,为什么能修改父进程 bash 的环境变量表?
一、基本概念
-
环境变量 (environment variables) 一般是指在操作系统中用来指定操作系统运行环境的一些参数
-
如:我们在编写 C/C++ 代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
-
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
二、讲解命令行参数
2.1. main 函数并不是程序的入口函数
在 main 函数,生成完可执行文件之后,输入 ./xxx 的时候,其实是可以加入一些参数的,其中 int main() 并不是全部的,() 里面还有参数,全称应该是这样写 int main(int argc,char* argv[]),这样一看 main 函数也是一个调用的函数,在程序运行的时候,最先执行的是 start函数,然后才是 main 函数。这个可以使用 反汇编或者是 gdb 动态调试出来,我们在这使用 gdb 动态调试,反汇编的命令也写出来,大家可以看看:
objdump -S myprocess > a.txt
gdb 动态调试:
g++ -g -o myprocess myprocess.cc
gdb ./myprocess
(gdb) break main
(gdb) run
(gdb) set backtrace past-main on
(gdb) set backtrace past-entry on
(gdb) bt

通过上面的调试信息内容,我可能可以得出结论:main函数不是程序的入口点,ELF 文件的入口点是 _start。_start 函数负责调用 C 运行时库的 __libc_start_main ,该函数在完成初始化后,最终调用用户的 main 函数。
2.2. 真实的 main 函数样子 --- main(int argc,char* argv[])
2.2.1. 简单代码样式
#include <iostream>
#include <cstdio>
#include <unistd.h>
using namespace std;
int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++)
{
cout << argv[i] << endl;
}
return 0;
}
我们输入这个命令,看看 打印输出的结果是什么:
./myprocess a b c

其中 a b c,bash 会自动进行切分。
此时就有疑问,需要这些命令行参数干什么?
2.2.2. 命令行参数的作用
用于实现程序不同子功能的方法
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("Usage: %s [-a] [-b] [-c] ...\n", argv[0]);
return 0;
}
// 选项标志(可改为函数指针数组,但这里直接执行)
for (int i = 1; i < argc; i++)
{
const char *arg = argv[i];
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");
}
else
{
printf("%s: 未知选项 %s\n", argv[0], arg);
}
}
return 0;
}
此时输入这个命令,看看效果:
./myprocess -a -b -d

此时我们就知道了,_start 大体上是怎么进行使用的, 下面是一个简单的伪代码:
_start
{
int ret = 0;
int arg_count = 0;
arg_count = 3; //得到的参数个数
if (arg_count == 0)
{
ret = main();
}
else if (arg_count == 2)
{
ret = main(argc, argv);
}
else
{
ret = main(argc, argv, env);
}
}
伪代码中的 if/else 分支,对应了 C/C++ 标准及 GNU 扩展所支持的 main 函数的三种有效签名:
| 分支条件 | 对应 main 签名 |
说明arg_count |
|---|---|---|
| arg_count == 0 | int main(void) | 最简形式,不接受任何参数(实际上在 Linux 中极少出现)。 |
| arg_count == 2 | int main(int argc,char* argv[] ) | 标准形式,接收命令行参数个数和字符串数组。 |
| else (其他情况) | int main(int argc,char* argv[] ,char*envp[] ) | GNU 扩展形式,额外接收环境变量数组。 |
因此我们得知:启动代码会根据实际情况,自动匹配你的 main 函数签名。如果你的 main 只写 void ,它就走第一个分支;如果你写 argc / argv,就走第二个。
2.2.3. 命令行参数表

三、引出环境变量
问题 - 为什么系统的命令 ls /pwd / mkdir 不用加 ./ 而我们自己写的 code 需要 ./code
我们需要知道对应 命令的 位置
输入以下命令:
which ls
which mkdir

我们会发现,他们都存储在 /usr/bin 路径下,如果我们把 自己写的可执行文件也放入到 该路径下,是不是就可以不加入 ./ 前缀了,我们试一下:
sudo cp myprocess /usr/bin/
myprocess -a -b -d

此时我们才得到今天的主题:这个就是 环境变量!
四、环境变量的相关知识
4.1. 常见的环境变量
- PATH:指定命令的搜索路径
- HOME:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL:当前Shell,它的值通常是/bin/bash。
4.2. 查看环境变量
4.2.1. 查看所有的环境变量
env

里面的内容蛮多的。
4.2.2. echo $NAME
echo $NAME //NAME:你的环境变量名称
例如:echo $SHELL

测试HOME
1. 用root和普通用户,分别执行 echo $HOME,对比差异
2. 执行 cd ~; pwd,对应 ~ 和 HOME 的关系


我们会发现,其实 $HOME 就是 ~ ,$HOME 和 ~ 只是同一个路径的两种表达方式(一个变量,一个快捷键)
4.2.3 如何直接运行可执行程序
有两种方法:
- 把可执行程序放到到 /usr/bin/ (不推荐)
- 把可执行程序所在的路径,放入到 PATH 中
我们现在使用第二个方法进行:
输入以下命令:
echo $PATH
PATH=/home/xxhh/review/linux
myprocess -a -b -c

但是此时我们执行下面的命令,就会发现一些问题以及问题的根源
ls
pwd
echo $PATH

- ls 命令找不到了
- pwd 可以找到,因为他是个 内联函数,以后再说
- 原因是:PAHT 被覆盖了
这个改变是内存级的,不用担心,把终端关闭,再打开一个新的,就恢复到原来的样子了。
-
环境变量是“内存级”的:你在终端里用 export 或 PATH = xxx 修的改,只改当前 Shell 进程的内存。重启电脑或打开新终端,改动就没了(恢复成系统默认)。
-
变量从配置文件来:每次登录时,Bash 进程会去硬盘上读取固定的配置文件(比如 ~/.profile 、 ~/.bashrc),用文件里的内容重新构建内存里的环境变量。这也是“重启后自动恢复默认”的原因。
-
执行顺序:登录时 先执行 ~/.profile,在执行 ~/.profile 的过程中,又调用了 ~/.bashrc。
Linux 系统默认的用户配置文件 ~/.profile
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
PATH="$HOME/.local/bin:$PATH"
fi
如何解决这个问题呢,输入以下的命令即可:
echo $PATH
PATH=$PATH:/home/xxhh/review/linux
echo $PATH
ls
pwd
myprocess -a -b -c


登录的时候,bash进程就会被创建,就会读取 .bashrc 和 .profile 里面的内容,构建出环境变量 PATH的本质是,系统内搜索指定命令的一个执行命令环境变量值 。bash 会有两个表,一个是环境变量表,一个是命令行参数表,内部都是字符串。
4.3. 认识更多的环境变量
| 变量 | 值示例 | 解释 |
|---|---|---|
| SHELL | /bin/bash | shell 脚本的位置(当前使用的 Shell 解释器路径) |
| HISTSIZE | 1000 | 最多可以存放历史命令的最大个数 |
| PWD | /home/xxhh/review/linux | 当前工作目录(pwd 命令显示的内容) |
| LOGNAME | xxhh | 用户登录名 |
| HOME | /home/xxhh | 家目录 |
| USER | xxhh | 用户名(通常与 LOGNAME 相同) |
| PATH | /usr/local/sbin:/usr/local/bin:/usr/sbin:...... | 命令搜索路径(系统查找可执行程序的目录列表) |
| OLDPWD | /home/xxhh | 上一个路径的地址(执行 cd 之前所在的目录) |
4.4. 相关命令行和系统接口
4.4.1. 相关命令行
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
输入以下命令:
export myEnv=1122
echo $myEnv

4.4.2. 代码
1. 利用参数列表,来获取环境变量
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstring>
using namespace std;
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
for (int i = 0; env[i]; i++)
{
cout << "env[" << i << "]-> " << env[i] << endl;
}
return 0;
}

其中,我们自己写的代码,编译成可执行程序,执行后,他的父进程是 bash,有一个命令行参数表和环境变量表。bash 为了给子进程做命令行解析,会把命令行解析传递给我们,同时在登陆的时候,就已经有了环境变量表,也会把环境变量表传递给子进程。
2. getenv

DESCRIPTION
The getenv() function searches the environment list to find the environment variable name, and returns a pointer to the corresponding value string.描述
getenv() 函数在环境列表中搜索名为 name 的环境变量,并返回指向对应值字符串的指针。
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
using namespace std;
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
(void)env;
char *val = getenv("PATH");
if (val == nullptr)
{
return -1;
}
else
{
cout << "PATH = " << val << endl;
}
return 0;
}

写一个程序,满足某个功能只能让特定的人使用,其他用户不行。
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
using namespace std;
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
(void)env;
const char *who = getenv("USER");
if (who == NULL)
return 1;
if (strcmp(who, "xxhh") == 0)
{
printf("用户名: %s, 该功能只能xxhh用户使用\n", who);
}
else
{
printf("该用户%s不能使用这个功能\n", who);
}
return 0;
}
3. environ

-
environ 本身是一个二级指针,它指向一个以 NULL 结尾的字符指针数组。
- 数组中的每个元素都是格式为 "变量名 = 值" 的字符串

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
using namespace std;
int main(int argc, char *argv[], char *env[])
{
(void)argc;
(void)argv;
(void)env;
// 注意:不需要接收 env 参数
for (int i = 0; environ[i] != NULL; i++)
{
printf("env[%d]: %s\n", i, environ[i]);
}
return 0;
}
4.5. 环境变量通常是具有全局属性的
通常是可以被子进程进行继承的
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
using namespace std;
extern char **environ; // 必须加入声明
int main(int argc, char *argv[])
{
if (fork() == 0)
{
printf("子进程开始打印 env\n");
for (int i = 0; environ[i]; i++)
{
printf("environment[%d] ->%s\n", i, environ[i]);
}
}
printf("父进程睡眠3s\n");
sleep(3);
return 0;
}

4.6. 一些其他问题
4.6.1. 本地变量与环境变量的区别
做下面的一组实验:
输入以下命令:
i=11
echo $i
set | grep i=
env | grep i=



-
本地变量(Local Variable):只在当前 Shell 进程中有效,不会被 fork() 创建的子进程继承。
示例中 i=11 就是本地变量,用 env | grep i= 查不到,因为它不在环境变量表里。 -
环境变量(Environment Variable):会被子进程继承,存在于环境变量表中(可通过 env 查看)。
4.6.2. export 的作用:将本地变量提升为环境变量
如果想将本地变量提升为环境变量,需要输入以下命令:
export i
env | grep i=

-
export i 把本地变量 i 导入到当前 Shell 的环境变量表中,之后子进程就能看到它了。
验证:env | grep i= 此时能输出 i=11。
4.6.3. 关键困惑:export 是子进程,为什么能修改父进程 bash 的环境变量表?
核心答案:export 是 Shell 的内建命令,不会创建子进程,由 bash 自己执行。
-
像 ls、myprocess 这种外部命令,会通过 fork() + exec() 创建子进程来运行,父子进程内存独立,子进程无法修改父进程的环境变量表。
-
但 export、cd、echo 等是 bash 自己内部实现的,由 bash 进程自身直接调用函数或系统调用,不产生子进程,所以它可以修改 bash 自己的环境变量表。
4.6.4. 补充示例:脚本中的本地变量 vs 子进程
现在有个问题:
当我们输入 export i 的时候,这个命令是子进程,父进程是 bash,bash 的环境变量表 子进程可以使用,但是 子进程的输入 export i 这个 i 变量可以给 bash,并放到环境变量表中,进程之间不是具有独立性?
-
脚本 test.sh 内容:
#!/bin/bash touch file mv file myfile i=100 echo $i -
执行 bash test.sh 时,会创建一个子进程(新的 bash 解释器)来运行脚本。
-
脚本内定义的 i=100 是该子进程的本地变量,脚本执行完毕后子进程退出,不影响父 bash 的环境变量表。
-
所以执行完脚本后,ls 看到文件操作(touch、mv)生效了,但变量 i 不会留在父 bash 中。

完
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)